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

В чем разница между глобальными переменными в C и С++?

Я проверил следующий код:

в файле a.c/a.cpp

int a;

в файле b.c/b.cpp

int a;
int main() { return 0; }

Когда я компилирую исходные файлы с помощью gcc *.c -o test, он преуспевает.

Но когда я компилирую исходные файлы с помощью g++ *.c -o test, он терпит неудачу:

ccIJdJPe.o:b.cpp:(.bss+0x0): multiple definition of 'a'
ccOSsV4n.o:a.cpp:(.bss+0x0): first defined here
collect2.exe: error: ld returned 1 exit status

Я действительно смущен этим. Есть ли разница между глобальными переменными в C и С++?

4b9b3361

Ответ 1

Вот соответствующие части стандарта. См. Мои объяснения ниже стандартного текста:

§6.9.2/2 Определение внешних объектов

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

ISO C99 §6.9/5 Внешние определения

Внешнее определение - это внешнее объявление, которое также является определением функции (отличной от встроенного определения) или объекта. Если идентификатор, объявленный с внешней связью, используется в выражении (отличном от части операнда оператора sizeof, результат которого является целочисленной константой), где-то во всей программе должно быть ровно одно внешнее определение для идентификатора; в противном случае должно быть не более одного.

С версией C глобальные переменные 'g' объединены в один, поэтому в конце дня вы будете иметь только один, который объявляется дважды. Это нормально из-за времени, когда экстерн не нужен или, возможно, не выходил. Следовательно, это связано с историей и соображениями совместимости для создания старого кода. Это расширение gcc для этой устаревшей функции.

В основном это означает, что gcc выделяет память для переменной с именем "a", поэтому может быть более одного объявления, но только одно определение. Вот почему приведенный ниже код не будет работать даже с gcc.

Это также называется предварительным определением. С С++ такого не существует, и это пока оно компилируется. С++ не имеет понятия условной декларации.

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

Обратите внимание, однако, что следующий код не будет компилироваться даже с gcc, потому что это уже предварительное определение/объявление с назначенными значениями:

в файле "a.c/a.cpp"

int a = 1;

в файле "b.c/b.cpp"

int a = 2;
int main() { return 0; }

Пойдем даже дальше этого с дальнейшими примерами. Следующие утверждения показывают нормальные определения и предварительные определения. Обратите внимание: статичность будет немного отличаться, поскольку это область файлов и больше не будет внешней.

int i1 = 10;         /* definition, external linkage */
static int i2 = 20;  /* definition, internal linkage */
extern int i3 = 30;  /* definition, external linkage */
int i4;              /* tentative definition, external linkage */
static int i5;       /* tentative definition, internal linkage */

int i1;              /* valid tentative definition */
int i2;              /* not legal, linkage disagreement with previous */
int i3;              /* valid tentative definition */
int i4;              /* valid tentative definition */
int i5;              /* not legal, linkage disagreement with previous */

Более подробную информацию можно найти на следующей странице:

http://c0x.coding-guidelines.com/6.9.2.html

Смотрите также эту запись в блоге для более подробной информации:

http://ninjalj.blogspot.co.uk/2011/10/tentative-definitions-in-c.html

Ответ 2

gcc реализует устаревшую функцию, в которой неинициализированные глобальные переменные помещаются в общий блок.

Хотя в каждой единицы перевода определения являются ориентировочными, в ISO C, в конце единицы перевода, предварительные определения "обновляются" до полных определений, если они еще не были объединены в условное определение.

В стандарте C всегда неверно иметь одни и те же переменные с внешней связью, определенные в большей части одной единицы перевода, даже если эти определения взяты из предварительных определений.

Чтобы получить то же поведение, что и С++, вы можете использовать переключатель -fno-common с gcc, и это приведет к той же ошибке. (Если вы используете GNU-компоновщик и не используете -fno-common, вы также можете рассмотреть возможность использования опции --warn-common/-Wl,--warn-common, чтобы выделить поведение времени ссылки при столкновении нескольких общих и не общих символов с тем же имя.)

На странице gcc man:

-fno-common

В коде C управляет размещением неинициализированного глобального переменные. Компиляторы Unix C традиционно допускали множественные      определения таких переменных в разных единицах компиляции посредством      помещая переменные в общий блок. Это поведение      указанный -fcommon, и по умолчанию используется для GCC      цели. С другой стороны, это поведение не требуется по ISO      C, а на некоторых объектах может быть нанесен штраф скорости или размера кода      переменные ссылки. Параметр -fno-common указывает, что      компилятор должен помещать неинициализированные глобальные переменные в данные      раздел объектного файла, а не генерировать их как общие      блоки. Это приводит к тому, что если одна и та же переменная объявлена      (без extern) в двух разных компиляциях, вы получите      ошибка множественного определения, когда вы их связываете. В этом случае вы      должен компилироваться с помощью -fcommon. Компиляция с      -fno-common полезен для целей, для которых он обеспечивает лучшую      производительности, или если вы хотите проверить, что программа будет работать      на других системах, которые всегда обрабатывают неинициализированную переменную      объявления таким образом.

поведение gcc является общим и описано в Приложении J стандарта (что не является нормативным), которое описывает обычно реализованные расширения стандарта:

J.5.11 Несколько внешних определений

Может быть более одного внешнего определения для идентификатора объекта, с или без явного использования ключевого слова extern; если определения не совпадают или больше, чем один инициализирован, поведение undefined (6.9.2).