Предварительные определения в C и связывание
-
18-09-2019 - |
Вопрос
Рассмотрим программу на языке 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 в обоих файлах.
Я сомневаюсь, что это произошло случайно или просто как «простая» функция, которую можно добавить в дополнение к тому, что требует стандарт.Если задуматься, это значит, что в компоновщике есть специальная поддержка тех глобальных переменных, которые не имеют инициализатора, в отличие от тех, которые явно инициализируются нулем.Кто-то сказал мне, что функция компоновщика в любом случае может потребоваться для компиляции Фортрана.Это было бы разумное объяснение.
Есть мысли по этому поводу?Другие интерпретации стандарта?Названия платформ, на которых файлы f1.c
и f2.c
отказаться быть связанными друг с другом?
Примечание:это важно, поскольку вопрос возникает в контексте статического анализа.Если два файла могут отказаться связываться на какой-то платформе, анализатор должен пожаловаться, но если каждая платформа компиляции принимает это, то нет причин об этом предупреждать.
Решение
Смотрите также Что такое внешние переменные в C.Это упоминается в стандарте 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
РЕДАКТИРОВАТЬ:Вывод NM для файла объекта, скомпилированного из «Extern int x;»; где x фактически используется в одной из функций
00000000 T _main
U _unknown
U _x
U dyld_stub_binding_helper