Почему следующее не компилируется?
...
extern int i;
static int i;
...
но если вы измените порядок, он скомпилируется нормально.
...
static int i;
extern int i;
...
Что здесь происходит?
Это специально приводится в качестве примера в стандарте 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
он находит несовместимое утверждение, что у него есть внутренняя связь, и выдает ошибку.
Другими словами, это потому, что объявления «мягче», чем определения. Например, вы можете объявить одно и то же несколько раз без ошибок, но вы можете определить это только один раз.
То же самое в Си, я не знаю (но ответ сетевого кодера ниже сообщает нам, что стандарт Си содержит то же самое требование).
Для C, цитируя стандарт, в C11 6.2.2: Связь идентификаторов:
3) Если объявление идентификатора области файла для объекта или функции содержит спецификатор класса хранения
static
, идентификатор имеет внутреннюю связь.4) Для идентификатора, объявленного со спецификатором класса хранилища
extern
в области видимости, в которой видно предыдущее объявление этого идентификатора, если в предыдущей декларации указана внутренняя или внешняя связь, связь идентификатора в последующем объявлении такая же, как
связь, указанная в предыдущей декларации. Если никакое предыдущее объявление не видно или если в предыдущем объявлении не указана связь, то идентификатор имеет внешнюю связь.
(Курсив мой)
Это объясняет второй пример (i
будет иметь внутреннюю связь). Что касается первого, я уверен, что это неопределенное поведение:
7) Если внутри единицы перевода появляется один и тот же идентификатор как с внутренней, так и с внешней связью, поведение не определено.
…так как extern
появляется до идентификатор объявлен с внутренней связью, 6.2.2 / 4 не применяется. В качестве таких, i
имеет как внутреннюю, так и внешнюю связь, так что это UB.
Если компилятор выдает диагностику, я думаю, вам повезло. Он может компилироваться без ошибок и при этом соответствовать стандарту.
В 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 ++ молчит только в определенном порядке, но в любом случае обе строки не должны быть в одном и том же исходном коде.
C ++:
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
[...]