Подтвердить что ты не робот

Можно ли объявить переменную как статическую, так и внешнюю?

Почему следующее не компилируется?

...
extern int i;
static int i;
...

но если вы отмените порядок, он компилируется отлично.

...
static int i;
extern int i;
...

Что здесь происходит?

4b9b3361

Ответ 1

Это специально приведено в качестве примера в стандарте С++, когда он обсуждает тонкости объявления внешней или внутренней связи. Он в разделе 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 содержит одно и то же требование).

Ответ 2

Для C, цитируя стандарт, в C11 6.2.2: Связывание идентификаторов:

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

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

(курсив мой)

Это объясняет второй пример (i будет иметь внутреннюю связь). Что касается первого, я уверен, что это поведение undefined:

7) Если в пределах единицы перевода появляется один и тот же идентификатор с внутренней и внешней связью, поведение undefined.

... потому что extern появляется перед объявлением идентификатора с внутренней связью, 6.2.2/4 не применяется. Таким образом, i имеет как внутреннюю, так и внешнюю связь, поэтому UB.

Если компилятор выдает диагностику, вам, к счастью, повезет. Он может скомпилировать оба без ошибок и по-прежнему соответствовать стандарту.

Ответ 3

В 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++ молчат только в определенном порядке, но в любом случае вы не должны иметь обе строки в одном исходном коде.

Ответ 4

С++:

7.1.1 Спецификаторы класса хранения [dcl.stc]

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
[...]