Есть ли в C одно правило определения, подобное C++?
Недавно я обнаружил, что есть некоторые случаи, которые будут абсолютно нарушать ODR C++, но будут компилироваться нормально в компиляторе C.
Например, этот странный сценарий (со мной):
Источник 1
int var_global=-3;
Источник 2
#include <stdio.h>
#include <conio.h>
unsigned int var_global;
int main() {
printf("%d \n",var_global);
getch();
return 0;
}
У меня есть напечатанный результат -3
(хотя в источнике 2 var_global
является unsigned
) и нет ошибки в отношении переопределения var_global
,
Я знал, что у C разные правила с C++, но я не думаю, что это так по-другому.
Я имею Google и прочитал много результатов, но нет официального результата, как этот C++.
Итак, вопрос:
Есть ли в C одно правило определения, подобное C++?
а также:
Как это называется официально?
Мне нужно сравнить его с правилом C++, чтобы я мог глубже понять оба языка.
p / s: я использовал Visual Studio 2010 для компиляции кода выше.
2 ответа
Я думаю, что вы ищете, глава §6.2.7 из C11
стандартный, совместимый тип и составной тип, (выделение мое)
Все объявления, которые ссылаются на один и тот же объект или функцию, должны иметь совместимый тип; в противном случае поведение не определено.
и связанные с совместимым типом,
Два типа имеют совместимый тип, если их типы одинаковы.
В твоем случае, int
а также unsigned int
не совместимые типы. Отсюда и неопределенное поведение.
Просто чтобы добавить немного ясности, в вашем источнике 2, unsigned int var_global;
является объявлением, и оно не соответствует другому склонению (и определению), так что это UB.
Тем не менее, заявление как
printf("%d \n",var_global);
всегда будет рассматривать аргумент %d
быть типом int
, Если тип и спецификатор формата не совпадают, вы снова вызовете неопределенное поведение.
РЕДАКТИРОВАТЬ:
После редактирования ответ: используйте -fno-common
чтобы получить желаемую ошибку. (отсутствует extern
это то, что вас беспокоит, я полагаю).
Цитирование из онлайн руководства GCC,
-fno-common
В коде C контролирует размещение неинициализированных глобальных переменных. Компиляторы Unix C традиционно разрешают множественные определения таких переменных в разных единицах компиляции, помещая переменные в общий блок. Это поведение, указанное -fcommon, и по умолчанию для GCC для большинства целей. С другой стороны, это поведение не требуется ISO C, и на некоторых целях может иметь место снижение скорости или размера кода для ссылок на переменные. Опция -fno-common указывает, что компилятор должен помещать неинициализированные глобальные переменные в раздел данных объектного файла, а не генерировать их как общие блоки. Это приводит к тому, что если объявлена одна и та же переменная (без
extern
) в двух разных компиляциях вы получаете ошибку множественного определения, когда связываете их. В этом случае вы должны скомпилировать с помощью -fcommon. Компиляция с -fno-common полезна для целей, для которых она обеспечивает лучшую производительность, или если вы хотите убедиться, что программа будет работать в других системах, которые всегда обрабатывают объявления неинициализированных переменных таким образом.
Я не знаю ни одного упоминания о формулировках "одно правило определения" в стандарте C, но в целом вы можете посмотреть в приложении §J.5.11, " Несколько внешних определений",
Для идентификатора объекта может быть несколько внешних определений с явным использованием ключевого слова или без него.
extern
; если определения не согласны или более одного инициализированы, поведение не определено.
То, что вы видите, не имеет ничего общего с правилом с одним определением. Это связано с тем, что %d
ожидает значения со знаком и, следовательно, почти наверняка просто будет рассматривать его как значение со знаком в вашей реализации.
Однако на это не стоит полагаться. Согласно стандарту C 7.19.6.1 The fprintf function /9
(Я ссылаюсь на C99, но C11 почти одинаков с точки зрения аспектов, показанных здесь):
Если какой-либо аргумент не является правильным типом для соответствующей спецификации преобразования, поведение не определено.
Поскольку вы используете неопределенное поведение, реализация может делать все, что захочет. Кроме того, в стандарте также конкретно указано, что это неопределенное поведение, если (из приложения J):
два объявления одного и того же объекта или функции указывают типы, которые не совместимы.
В вашем случае эти два объявления указывают один и тот же объект, поскольку они оба имеют внешнюю связь.
Теперь вы можете подумать, что целые числа со знаком и без знака будут совместимы, но вы ошибаетесь: 6.2.7 Compatible and composite type
, а также 6.2.5 Types
проясняет, что подписанные и неподписанные варианты не совместимы:
Два типа имеют совместимый тип, если их типы одинаковы.
Для каждого из целочисленных типов со знаком существует соответствующий (но другой) целочисленный тип без знака (обозначенный ключевым словом unsigned), который использует тот же объем памяти (включая информацию о знаке) и имеет те же требования выравнивания.