GCC не жалуется на переопределение внешней переменной?

Этот простой код (MCVE):

#include <stdio.h>

int a = 3;
int main(){
    printf("%d\n", a);
    return 0;
}
int a; // This line

К моему удивлению, GCC (MinGW GCC 4.8.2, 4.9.2 и 6.3.0) не выдает никаких ошибок, даже предупреждений о отмеченной строке! Однако это произойдет, если я присвою значение a во втором определении.

Что еще более странно, g++ говорит мне, что второе переопределение является ошибкой, а gcc - нет.

Разве это не должно быть повторное определение существующей переменной из-за отсутствия ключевого слова extern?


person iBug    schedule 14.11.2017    source источник
comment
неинициализированные глобальные объекты неявно extern.   -  person Ctx    schedule 14.11.2017
comment
Было бы жаловаться, если бы у обоих были инициализаторы. При таком написании требуется не жаловаться. Второе - предварительное определение. Обычно вы сначала видите неинициализированное объявление, но все в порядке, как написано.   -  person Jonathan Leffler    schedule 14.11.2017
comment
если неинициализированные глобальные объекты неявно extern, это будет означать, что int a; ... int a; будет эквивалентно extern int a; ... extern int a;, но последний не связывает.   -  person Jabberwocky    schedule 14.11.2017


Ответы (2)


Из стандарта C (6.9.2 Определения внешних объектов)

1 Если объявление идентификатора для объекта имеет файловую область и инициализатор, объявление является внешним определением идентификатора.

и

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

И есть пример в стандарте C

int i1 = 1; // definition, external linkage
//...
int i1; // valid tentative definition, refers to previous

Итак, в вашей программе это одно объявление

int a = 3;

внешнее определение идентификатора a

и этот

int a;

это предварительное определение, которое относится к предыдущему внешнему определению идентификатора.

Если использовать инициализатор во втором объявлении, вы получите два внешних определения для идентификатора, и компилятор выдаст ошибку, потому что может существовать только одно внешнее определение.

Учтите, что C и C ++ различаются по отношению к этому контексту,

Из стандарта C ++ (C.1.2, пункт 6: основные концепции)

6.1

Изменение: C ++ не имеет «предварительных определений», как в C. Например, в области видимости файла,

int i;
int i;

допустимо в C, недопустимо в C ++.

person Vlad from Moscow    schedule 14.11.2017
comment
Значит, второй int a; может быть записан как static int a; с тем же эффектом (неявный extern)? - person iBug; 14.11.2017
comment
@iBug Его нельзя записать с ключевым словом static, потому что оно уже имеет внешнее определение. Один и тот же идентификатор может не иметь внешней и внутренней связи в одной и той же единице перевода. - person Vlad from Moscow; 14.11.2017
comment
Примечательно, что будущие языковые направления C11: объявление идентификатора с внутренней связью в области файла без спецификатора статического класса хранения является устаревшей функцией. - person Lundin; 14.11.2017

В С. это называется предварительные определения.

Cppreference говорит:

Предварительные определения

Предварительное определение - это внешнее объявление без инициализатора и либо без спецификатора класса хранения, либо со спецификатором static.

Предварительное определение - это заявление, которое может действовать или не действовать как определение. Если фактическое внешнее определение встречается раньше или позже в той же единице перевода, то предварительное определение действует просто как декларация.

[...]

int i3; // tentative definition, external linkage

int i3; // tentative definition, external linkage 

extern int i3; // declaration, external linkage
person msc    schedule 14.11.2017