Предварительные определения в C и ссылки
Рассмотрим программу C, состоящую из двух файлов,
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 в обоих файлах.
Я сомневаюсь, что это происходит случайно, или просто как "простая" функция, предоставляемая в дополнение к тому, что требует стандарт. Если вы думаете об этом, это означает, что в компоновщике есть особая поддержка для тех глобальных переменных, которые не имеют инициализатора, в отличие от тех, которые явно инициализированы до нуля. Кто-то сказал мне, что функция компоновщика может понадобиться для компиляции Fortran. Это было бы разумным объяснением.
Любые мысли об этом? Другие толкования стандарта? Имена платформ, на которых файлы f1.c
и f2.c
отказываются связываться вместе?
Примечание: это важно, потому что вопрос возникает в контексте статического анализа. Если два файла могут отказаться от привязки на какой-либо платформе, анализатор должен жаловаться, но если каждая платформа компиляции принимает его, тогда нет никаких оснований предупреждать об этом.
Ответы
Ответ 1
См. также Что такое внешние переменные в C. Это упоминается в стандарте C в информационном приложении J в качестве общего расширения:
J.5.11 Несколько внешних определений
Может быть более одного внешнего определения для идентификатора объекта с явным использованием ключевого слова extern или без него; если определения не совпадают или несколько инициализировано, поведение undefined (6.9.2).
Предупреждение
Как указывает здесь @litb, и, как указано в моем ответе на вопрос с перекрестными ссылками, использование нескольких определений для глобальной переменной приводит к поведению undefined, что является стандартным способом сказать "что-нибудь может случиться". Одна из вещей, которая может случиться, заключается в том, что программа ведет себя так, как вы ожидаете; и J.5.11 говорит примерно так: "вам может повезти чаще, чем вы этого заслуживаете". Но программа, которая опирается на несколько определений внешней переменной - с или без явного ключевого слова "extern" - не является строго соответствующей программой и не гарантируется работать повсюду. Эквивалентно: в нем содержится ошибка, которая может показаться или не отображаться.
Ответ 2
В стандарте есть что-то, называемое "общим расширением", где допускается определение переменных несколько раз, пока переменная инициализируется только один раз. См. http://c-faq.com/decl/decldef.html
Связанная страница говорит, что это относится к платформам Unix - я думаю, это то же самое для c99 как c89, хотя, возможно, это было принято другими компиляторами для создания своего рода стандарта defacto. Интересно.
Ответ 3
Это объясняет мой ответ на комментарий 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;" где x фактически используется в одной из функций
00000000 T _main
U _unknown
U _x
U dyld_stub_binding_helper