Предварительные определения в 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