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

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

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

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

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

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

20

Решение

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

Другими словами, это потому, что объявления «мягче», чем определения. Например, вы можете объявить одно и то же несколько раз без ошибок, но вы можете определить это только один раз.

То же самое в Си, я не знаю (но ответ сетевого кодера ниже сообщает нам, что стандарт Си содержит то же самое требование).

16

Другие решения

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

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

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

(Курсив мой)

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

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

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

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

10

В Microsoft Visual Studio обе версии компилируются просто отлично.
На Gnu C ++ вы получаете ошибку.

Я не уверен, какой компилятор является «правильным». В любом случае, наличие обеих строк не имеет особого смысла.

extern int i означает, что целое число i определяется в каком-то другом модуле (объектный файл или библиотека). Это декларация. Компилятор будет не выделить хранилище i в этом объекте, но он распознает переменную, когда вы используете ее где-то еще в программе.

int i говорит компилятору выделить память для i, Это определение. Если другие файлы C ++ (или C) имеют int iЛинкер пожалуется, что int i определяется дважды.

static int i похож на выше, с дополнительной функциональностью, которая i местный К нему нельзя получить доступ из другого модуля, даже если они объявили extern int i, Люди используют ключевое слово static (в этом контексте), чтобы сохранить локализацию.

Следовательно, имея i оба они объявлены как определяемые где-то еще, И определено как статическое внутри модуля, похоже на ошибку. Visual Studio об этом молчит, а g ++ молчит только в определенном порядке, но в любом случае обе строки не должны быть в одном и том же исходном коде.

1

C ++:

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

7) Имя, объявленное в области пространства имен без спецификатора класса хранения, имеет внешнюю связь, если оно не имеет
внутренняя связь из-за предыдущего объявления
и при условии, что он не объявлен постоянным. Объекты объявлены
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
[...]
1
По вопросам рекламы [email protected]