Почему следующее не компилируется?
...
extern int i;
static int i;
...
но если вы отмените порядок, он компилируется отлично.
...
static int i;
extern int i;
...
Что здесь происходит?
Почему следующее не компилируется?
...
extern int i;
static int i;
...
но если вы отмените порядок, он компилируется отлично.
...
static int i;
extern int i;
...
Что здесь происходит?
Это специально приведено в качестве примера в стандарте С++, когда он обсуждает тонкости объявления внешней или внутренней связи. Он в разделе 7.1.1.7, который имеет это значение:
static int b ; // b has internal linkage
extern int b ; // b still has internal linkage
extern int d ; // d has external linkage
static int d ; // error: inconsistent linkage
В разделе 3.5.6 обсуждается, как extern
должен вести себя в этом случае.
Что происходит: static int i
(в данном случае) - это определение, где static
указывает, что i
имеет внутреннюю связь. Когда extern
происходит после static
, компилятор видит, что символ уже существует и принимает, что он уже имеет внутреннюю связь и продолжается. Вот почему ваш второй пример компилируется.
extern
, с другой стороны, является объявлением, он неявно утверждает, что символ имеет внешнюю связь, но на самом деле ничего не создает. Поскольку в вашем первом примере нет i
, компилятор регистрирует i
как имеющий внешнюю связь, но когда он добирается до вашего static
, он находит несовместимое утверждение о том, что он имеет внутреннюю связь и дает ошибку.
Иными словами, потому что декларации "более мягкие", чем определения. Например, вы можете объявлять одно и то же несколько раз без ошибок, но вы можете определить его только один раз.
Является ли это тем же самым в C, я не знаю (но ответ netcoder ниже сообщает нам, что стандарт C содержит одно и то же требование).
Для C, цитируя стандарт, в C11 6.2.2: Связывание идентификаторов:
3) Если объявление идентификатора области файла для объекта или функции содержит спецификатор класса хранения
static
, идентификатор имеет внутреннюю привязку.4) Для идентификатора, объявленного с помощью спецификатора класса хранения
extern
в области, в которой видимо предварительное объявление этого идентификатора, если предыдущее объявление указывает внутреннюю или внешнюю привязку, связь идентификатора с более поздним объявлением такая же, как и ссылка, указанная в предыдущем объявлении. Если никакое предварительное объявление не отображается или если в предыдущем объявлении не указано никакой привязки, то идентификатор имеет внешнюю привязку.
(курсив мой)
Это объясняет второй пример (i
будет иметь внутреннюю связь). Что касается первого, я уверен, что это поведение undefined:
7) Если в пределах единицы перевода появляется один и тот же идентификатор с внутренней и внешней связью, поведение undefined.
... потому что extern
появляется перед объявлением идентификатора с внутренней связью, 6.2.2/4 не применяется. Таким образом, i
имеет как внутреннюю, так и внешнюю связь, поэтому UB.
Если компилятор выдает диагностику, вам, к счастью, повезет. Он может скомпилировать оба без ошибок и по-прежнему соответствовать стандарту.
В Microsoft Visual Studio обе версии компилируются просто отлично. На Gnu С++ вы получите сообщение об ошибке.
Я не уверен, какой компилятор "правильный". В любом случае, наличие обеих линий не имеет большого смысла.
extern int i
означает, что целое число i
определено в некотором другом модуле (объектный файл или библиотека). Это декларация. Компилятор не будет выделять хранилище i
в этом объекте, но он распознает эту переменную, когда вы используете ее где-то еще в программе.
int i
сообщает компилятору выделить хранилище для i
. Это определение. Если в других файлах С++ (или C) есть int i
, компоновщик будет жаловаться, что int я определяется дважды.
static int i
аналогичен приведенному выше, с дополнительной функциональностью, которая i
является локальной. Он не может быть доступен из другого модуля, даже если они объявляют extern int i
. Люди используют ключевое слово static (в этом контексте), чтобы сохранить локализацию.
Следовательно, имея i
, объявленные как определенные где-то еще, И, определяемый как статический в модуле, кажется ошибкой. Visual Studio молчат об этом, а g++ молчат только в определенном порядке, но в любом случае вы не должны иметь обе строки в одном исходном коде.
С++:
7) Имя, объявленное в области пространства имен без спецификатора класса хранения, имеет внешнюю связь, если только оно не имеет внутренняя связь из-за предыдущей декларации и при условии, что она не объявлена как const. Объявленные объекты const, а не явно объявленный extern имеют внутреннюю связь.
Итак, первый пытается сначала предоставить внешнюю связь i
, а затем внутреннюю.
Второй сначала дает внутреннюю связь, а вторая строка не пытается дать ему внешнюю связь, потому что ранее была объявлена как внутренняя.
8) Связи, подразумеваемые последовательными декларациями для данного объекта, согласуются. То есть, в пределах данного объема, каждое объявление, объявляющее одно и то же имя переменной или ту же перегрузку имени функции, должно подразумевать та же связь. Однако каждая функция в заданном наборе перегруженных функций может иметь разную связь.
[Пример:
[...]
static int b; // b has internal linkage
extern int b; // b still has internal linkage
[...]
extern int d; // d has external linkage
static int d; // error: inconsistent linkage
[...]