Смешивание объявлений с внешним, статическим и отсутствующим спецификатором хранилища в глобальной области видимости

Я изучаю, когда можно смешивать переменные, объявленные с помощью extern , static и без спецификатора хранилища в глобальной области. Результаты оставили меня в замешательстве.

Это то, что я нашел (каждый абзац является отдельной единицей компиляции):

 /* ok */ int x; int x; /* ok */ int f(); int f(); /* ok */ int x; extern int x; /* ok */ int f(); extern int f(); /* error: static declaration follows non-static declaration */ int x; static int x; /* ok (no warning) */ int f(); static int f(); /* ok */ extern int x; int x; /* ok */ extern int f(); int f(); /* ok */ extern int x; extern int x; /* ok */ extern int f(); extern int f(); /* error: static declaration follows non-static declaration */ extern int x; static int x; /* error: static declaration follows non-static declaration */ extern int f(); static int f(); /* error: non-static declaration follows static declaration */ static int x; int x; /* ok (no warning) */ static int f(); int f(); /* ok */ static int x; extern int x; /* ok */ static int f(); extern int f(); /* ok */ static int x; static int x; /* ok */ static int f(); static int f(); 

Я получаю точные точные результаты с gcc и clang но я не могу найти шаблон в том, что работает и что не работает.

Есть ли здесь логика?

Что говорят стандарты C о смешивании глобальных деклараций, объявленных с помощью extern , static и без спецификатора хранилища?

Если вы определяете идентификатор без ключевого слова static , он публикуется в объектном файле и может быть доступен другим модулем. Поэтому, если вы снова определите этот идентификатор без static в другом модуле, вы получите конфликт: два опубликованных идентификатора.

Если вы используете ключевое слово static , то (большая часть) остальная часть этого не применяется.

Проблема заключается в различии между объявлением идентификатора и его определением . Первый говорит: «Идентификатор X с этим типом». Второй говорит: «Вот что я буду называть X этого типа».

  • С функциями это легко: не предоставляйте тело, а это просто декларация . Обеспечьте тело, и это тоже определение . Вы можете использовать extern чтобы сделать это явным в файле заголовка, но поскольку это значение по умолчанию, это не так.

  • С переменными это сложнее. Просто объявление переменной также определяет – таким образом, вы можете инициализировать ее при ее определении . Если вы хотите только объявить его, вам нужно использовать ключевое слово extern но вы также не можете его инициализировать. Вы говорите: «там будет переменная, называемая X », – так что вы тоже не можете принять решение об определении ее!

Вот почему в файлах заголовков все переменные должны быть явно объявлены как extern и static :

  • Первое обычно: будет общая переменная, к которой каждый может получить доступ. Не забывайте, что где-то в одном модуле вам нужно предоставить фактическое определение без ключевого слова extern с необязательным значением инициализации.
  • Вторая редкость: каждый модуль, содержащий заголовочный файл, будет иметь свою собственную, не конфликтующую переменную с этим конкретным именем. Компилятор может не выделять для него память (особенно, если она является константой), но если она выделяет память, она будет отличаться в каждом модуле. Зачем вам это делать? Возможно, (принудительные) встроенные функции этого файла заголовка нуждаются в каждом модуле, чтобы иметь свою собственную копию …

Прежде всего, в стандарте C ничего не называют «глобальным», это часто злоупотребляемый термин, который может означать несколько разных вещей.

Если вы объявляете что-то в области файлов (то, что вы называете «глобальным») и не указываете class хранения, то по умолчанию используется внешняя связь. Вы не можете объявить что-то без привязки к области файлов. Это указано в C11 6.2.2.

Переменные (акцент мой):

Если объявление идентификатора области файла для объекта или функции содержит спецификатор classа хранения static, идентификатор имеет внутреннюю привязку.

Для идентификатора, объявленного с помощью внешнего спецификатора classа хранения в области видимости, в которой видна предварительная декларация этого идентификатора, если предыдущее объявление указывает внутреннюю или внешнюю связь, связь идентификатора с последующим объявлением совпадает с привязкой указанных в предыдущем заявлении. Если ни одно предварительное объявление не отображается, или если в предыдущем объявлении не указана ссылка, то идентификатор имеет внешнюю привязку.

Функции:

Если декларация идентификатора для функции не имеет спецификатора classа хранения, ее привязка определяется точно так, как если бы она была объявлена ​​с помощью спецификатора classа хранения extern. Если объявление идентификатора для объекта имеет область действия файла и спецификатор classа хранения, его связь является внешней.