Введение
Стандарт C не определяет поведение определения идентификатора с внешней связью дважды.Некоторое поведение обычно определяется как расширение C, особенно в системах Unix.Однако это расширение опирается на определения, имеющие совместимые типы;результат определения int x;
и char x;
обычно не определяется.
Обсуждение
Определение идентификатора с внешней связью дважды нарушает ограничение в стандарте C, в C 2018 6,9 5 (добавлен жирный шрифт):
Если в выражении используется идентификатор, объявленный с внешней связью (кроме как как часть операнда оператора sizeof
или _Alignof
, результатом которого является целочисленная константа)где-то во всей программе должно быть ровно одно внешнее определение для идентификатора ;в противном случае их должно быть не более одного.
В вашей программе x
используется в выражении &x
, поэтому применимо указанное выше ограничение: должно быть ровно одно внешнее определение для x
.Когда ограничение нарушается, результирующее поведение не определяется стандартом C согласно C 2018 4 2.
Почему тогда int x;
и char x;
ведут себя иначе, чем int x = 0;
и char x = 0;
?Можно подумать, что они должны быть одинаковыми, потому что первые являются предварительными определениями (поскольку у них нет спецификатора или инициализатора класса хранения), а C 2018 6.9.2 2 говорит:
Если единица перевода содержит одинили более предварительные определения для идентификатора, и модуль перевода не содержит внешнего определения для этого идентификатора, тогда поведение точно такое, как если бы модуль перевода содержал объявление области файла этого идентификатора с составным типом на конец переводаединица, с инициализатором, равным 0.
Есть две причины.Первое - это правило о нарушении ограничения, которое приводит к тому, что поведение, не определенное стандартом C, является основным правилом;оно имеет приоритет над правилом о предварительных определениях.
Во-вторых, хотя стандарт С не определяет поведение, другие документы могут определять его.Как отмечено в C 2018 J.5.11 (который является информативным разделом, а не нормативной частью стандарта), общим расширением языка C является разрешение нескольких внешних определений.Как правило, типы определений должны совпадать, и только одно должно быть инициализировано.
Например, Двоичный интерфейс приложений Systems V описывает, как можно согласовать несколько определений в случаях, когда естьсмешанные сильные и слабые определения или смешанные общие и не общие определения.Компилятор сотрудничает с этим расширением C, создавая объектный файл, который помечает идентификаторы по-разному в зависимости от того, имеют ли они регулярные определения или только предварительные определения.Например, компиляция файла, содержащего char x;
с Apple LLVM 10.0.0 и clang-1000.11.45.5 для x86_64, производит символ x
, помеченный для общего раздела, но компиляция файла, содержащего int x = 0;
, производит символ x
отмечен для общего раздела.(Когда команда nm
применяется к объектному файлу, созданному компилятором, для этих разделов отображаются C
и S
соответственно.)
Сводка
В результате получается:
- Определение
x
дважды не определяется стандартом C. - Компилятор и компоновщик расширяют стандарт C, чтобы разрешить несколько предварительных определений
x
наряду с максимальноодно регулярное определение. - Несмотря на расширение, поведение определения
x
с int
в одном месте и char
в другом месте является неправильным, но не диагностируется компоновщиком.