Ответ 1
Вы нарушаете C "одно правило определения", а результат - undefined. "Одно правило определения" официально не указано в стандарте как таковом. Мы рассматриваем объекты в разных исходных файлах (ака, единицы перевода), поэтому мы имеем дело с "внешними определениями". Семантика "одного внешнего определения" изложена (C11 6.9 p5):
Внешнее определение - это внешнее объявление, которое также является определением функции (отличной от встроенного определения) или объекта. Если идентификатор, объявленный с внешней связью, используется в выражении (кроме как в части операнда оператора
sizeof
или_Alignof
, результат которого является константой целого), где-то во всей программе должно быть точно одно внешнее определение для идентификатора; в противном случае должно быть не более одного.
В основном это означает, что вам разрешено определять объект не более один раз. (В противном случае вы можете вообще не определять внешний объект, если он никогда не используется нигде в программе.)
Обратите внимание, что у вас есть два внешних определения для b
. Одна из них - это структура, которую вы инициализируете в foo.c
, а другая - предварительное определение в main.c
, (C11 6.9.2 p1-2):
Если объявление идентификатора для объекта имеет область действия файла и инициализатор, Объявление является внешним определением идентификатора.
Объявление идентификатора для объекта с файловой областью без инициализатора и без спецификатора класса хранения или спецификатором класса хранения
static
представляет собой предварительное определение. Если единица перевода содержит одно или несколько предварительных определений для идентификатора, а единица перевода не содержит внешнего определения для этого идентификатора, то поведение в точности совпадает с тем, что единица перевода содержит объявление области файла этого идентификатора, причем составной тип как конца блока перевода, причем инициализатор равен 0.
Итак, у вас есть несколько определений b
. Однако есть еще одна ошибка, поскольку вы определили b
с разными типами. Прежде всего обратите внимание, что допускается множественное объявление одного объекта с внешней связью. Однако, когда одно и то же имя используется в двух разных исходных файлах, это имя относится к одному и тому же объекту (C11 6.2.2 p2):
В наборе единиц перевода и библиотеках, составляющих всю программу, каждый объявление определенного идентификатора с внешней связью обозначает тот же объект или функция.
C строго ограничивает объявления одним и тем же объектом (C11 6.2.7 p2):
Все объявления, относящиеся к одному и тому же объекту или функции, должны иметь совместимый тип; в противном случае поведение undefined.
Так как типы для b
в каждом из ваших исходных файлов фактически не совпадают, поведение undefined. (Что представляет собой совместимый тип, подробно описан во всем C11 6.2.7, но в основном он сводится к тому, что типы должны соответствовать.)
Итак, у вас есть два отказа для b
:
- Несколько определений.
- Несколько объявлений с несовместимыми типами.
Технически ваше объявление int a
в обоих исходных файлах также нарушает "одно правило определения". Обратите внимание, что a
имеет внешнюю связь (C11 6.2.2 p5):
Если объявление идентификатора для объекта имеет область действия файла и спецификатор класса хранения, его связь является внешней.
Но, из цитаты из C11 6.9.2 ранее, те int a
предварительные определения являются внешними определениями, и вы можете разрешить только одну из цитат из C11 6.9 вверху.
Обычные заявления об отказе применяются для поведения undefined. Все может случиться, и это будет включать поведение, которое вы наблюдали.
Общим расширением для C является разрешение множества внешних определений и описано в стандарте C в информационном приложении J.5 (C11 J.5.11):
Может быть более одного внешнего определения для идентификатора объекта, с или без явного использования ключевого слова
extern
; , если определения не соответствуют, или несколько инициализировано, поведение undefined (6.9.2).
(Акцент мой.) Поскольку определения для a
согласны, там нет никакого вреда, но определения для b
не согласуются. Это расширение объясняет, почему ваш компилятор не жалуется на наличие нескольких определений. Из цитаты C11 6.2.2 компоновщик попытается согласовать множественные ссылки на один и тот же объект.
Обычно компоновщики используют одну из двух моделей для согласования нескольких определений одного и того же символа в нескольких единицах перевода. Это "Общая модель" и "Модель Ref/Def". В "Общей модели" несколько объектов с тем же именем складываются в один объект в стиле union
, так что объект принимает размер самого большого определения. В "Ref/Def Model" каждое внешнее имя должно иметь ровно одно определение.
Инструментальная программа GNU по умолчанию использует "общую модель" и "Relaxed Ref/Def Model", где она применяет строго одно правило определения для одной единицы перевода, но не жалуется на нарушения в нескольких единицах перевода.
"Общая модель" может быть подавлена в компиляторе GNU с помощью параметра -fno-common
. Когда я тестировал это в своей системе, это вызвало поведение "Строгое изображение Ref/Def Model" для кода, подобного вашему:
$ cat a.c
#include <stdio.h>
int a;
struct { char a; int b; } b = { 2, 4 };
void foo () { printf("%zu\n", sizeof(b)); }
$ cat b.c
#include <stdio.h>
extern void foo();
int a, b;
int main () { printf("%zu\n", sizeof(b)); foo(); }
$ gcc -fno-common a.c b.c
/tmp/ccd4fSOL.o:(.bss+0x0): multiple definition of `a'
/tmp/ccMoQ72v.o:(.bss+0x0): first defined here
/tmp/ccd4fSOL.o:(.bss+0x4): multiple definition of `b'
/tmp/ccMoQ72v.o:(.data+0x0): first defined here
/usr/bin/ld: Warning: size of symbol `b' changed from 8 in /tmp/ccMoQ72v.o to 4 in /tmp/ccd4fSOL.o
collect2: ld returned 1 exit status
$
Я лично считаю, что последнее предупреждение, предоставленное компоновщиком, должно всегда предоставляться независимо от модели разрешения для нескольких определений объектов, но это ни здесь, ни там.
Литература:
К сожалению, я не могу дать вам ссылку на мою копию стандарта C11
Что такое переменные extern
в C?
"Руководство для начинающих по компоновщикам"
Документация SAS по внешним переменным моделям