Я изучаю, когда можно смешивать переменные, объявленные с помощью 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а хранения, его связь является внешней.