Предварительные определения в C99 и ссылки
Рассмотрим программу на С, состоящую из двух файлов:
f1.c:
int x;
f2.c:
int x=2;
Мое прочтение пункта 6.9.2 стандарта C99 заключается в том, что эта программа должна быть отклонена. В моей интерпретации 6.9.2 переменная x
предварительно определено в f1.c
, но это предварительное определение становится фактическим определением в конце единицы перевода, и (на мой взгляд) должно вести себя так, как если бы f1.c
содержал определение int x=0;
,
Со всеми компиляторами (и, что важно, с компоновщиками) я смог попробовать, это не то, что происходит. На всех платформах компиляции, которые я пробовал, нужно связать два вышеупомянутых файла и значение x
2 в обоих файлах.
Я сомневаюсь, что это происходит случайно или просто как "простая" функция, предоставляемая в дополнение к требованиям стандарта. Если вы думаете об этом, это означает, что в компоновщике есть специальная поддержка для тех глобальных переменных, которые не имеют инициализатора, в отличие от тех, которые явно инициализированы нулем. Кто-то сказал мне, что функция компоновщика может быть необходима для компиляции Фортрана в любом случае. Это было бы разумным объяснением.
Есть мысли по этому поводу? Другие интерпретации стандарта? Названия платформ, на которых файлы f1.c
а также f2.c
отказаться быть связанным вместе?
Примечание: это важно, потому что вопрос возникает в контексте статического анализа. Если два файла могут отказать в связывании на какой-либо платформе, анализатор должен подать жалобу, но если каждая платформа компиляции принимает это, то нет причин предупреждать об этом.
3 ответа
Смотрите также Что такое внешние переменные в Си. Это упоминается в стандарте C в информативном Приложении J как общее расширение:
J.5.11 Несколько внешних определений
Для идентификатора объекта может быть несколько внешних определений с явным использованием ключевого слова extern или без него; если определения не совпадают или более одного инициализированы, поведение не определено (6.9.2).
Предупреждение
Как указывает здесь @litb и как указано в моем ответе на вопрос с перекрестными ссылками, использование нескольких определений для глобальной переменной приводит к неопределенному поведению, которое является стандартом для выражения "все может произойти". Одна из вещей, которая может произойти, заключается в том, что программа ведет себя так, как вы ожидаете; и в J.5.11 примерно сказано: "Возможно, вам повезет чаще, чем вы заслуживаете". Но программа, которая опирается на несколько определений внешней переменной - с явным ключевым словом extern или без него - не является строго соответствующей программой и не гарантирует, что она будет работать везде. Эквивалентно: это содержит ошибку, которая может показать или не показать себя.
В стандарте есть нечто, называемое "общим расширением", где допускается многократное определение переменных, если переменная инициализируется только один раз. См. http://c-faq.com/decl/decldef.html
На связанной странице написано, что это относится к платформам Unix - я думаю, что это то же самое для c99 и c89- хотя, возможно, оно было принято многими компиляторами для формирования своего рода стандарта де-факто. Интересно.
Это уточнить мой ответ на комментарий от olovb:
вывод nm для объектного файла, скомпилированного из "int x;". На этой платформе к символам добавляется символ "_", то есть переменная x отображается как _x.
00000000 T _main
U _unknown
00000004 C _x
U dyld_stub_binding_helper
вывод nm для объектного файла, скомпилированного из "int x=1;"
00000000 T _main
U _unknown
000000a0 D _x
U dyld_stub_binding_helper
вывод nm для объектного файла, скомпилированного из "int x=0;"
00000000 T _main
U _unknown
000000a0 D _x
U dyld_stub_binding_helper
вывод nm для объектного файла, скомпилированного из "extern int x;"
00000000 T _main
U _unknown
U dyld_stub_binding_helper
EDIT: вывод nm для объектного файла, скомпилированного из "extern int x;" где х фактически используется в одной из функций
00000000 T _main
U _unknown
U _x
U dyld_stub_binding_helper