Это немного расширяет некоторые другие ответы и делает некоторые моменты, которые другие пропустили.
Цитаты относятся к N1570 проекту стандарта ISO C 2011 года.Я не думаю, что произошли какие-либо существенные изменения в этой области со времени стандарта ANSI C 1989 года (который эквивалентен стандарту ISO C 1990 года).Ссылка типа «7.19p3» относится к подразделу 7.19, пункт 3. (Цитаты в этом вопросе относятся к стандарту ANSI 1989 года, в котором описан язык в разделе 3 и библиотека в разделе 4; все издания стандарта ISOописать язык в разделе 6 и библиотеку в разделе 7.)
7.19p3 требует, чтобы макрос NULL
расширился до "определенной константой реализации нулевого указателя".
6.3.2.3p3 говорит:
Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу void *
, называется константой нулевого указателя .
Поскольку константа нулевого указателя выделена курсивом, это определение термина (3p1 определяет это соглашение), что подразумевает, что ничто иное, кроме указанного, не может бытьконстанта нулевого указателя.(Стандарт не всегда строго следует этому соглашению для своих определений, но нет проблем, если предположить, что он делает это в этом случае.)
Так что, если мы «что-то дурацкое», нам нужно посмотреть на то, что можетбыть «выражением целочисленной константы».
Фраза константа нулевого указателя должна восприниматься как один термин, а не как фраза, значение которой зависит от составляющих ее слов.В частности, целочисленная константа 0
является константой нулевого указателя, независимо от контекста, в котором она появляется;он не должен приводить к нулевому указателю значение , и он имеет тип int
, а не указатель любого типа.
"Целочисленное константное выражение со значением 0" может быть любымиз множества вещей (бесконечно много, если мы игнорируем пределы емкости).Буква 0
является наиболее очевидной.Другие возможности: 0x0
, 00000
, 1-1
, '\0'
и '-'-'-'
.(Из формулировки не на 100% ясно, относится ли «значение 0» именно к этому значению типа int
, но я считаю, что консенсус заключается в том, что 0L
также является действительной константой нулевого указателя.)
Еще одно важное предложение - 6.6p10:
Реализация может принимать другие формы константных выражений.
Это не совсем понятно (для меня)сколько широты это должно позволить.Например, компилятор может поддерживать двоичные литералы как расширение;тогда 0b0
будет действительной константой нулевого указателя.Это может также разрешить ссылки в стиле C ++ на const
объекты, так что при заданной
const int x = 0;
ссылка на x
может быть константным выражением (это не соответствует стандарту C).
Итак, ясно, что 0
является константой нулевого указателя, и что это допустимое определение для макроса NULL
.
В равной степени ясно, что (void*)0
является константой нулевого указателя, но это не допустимое определение для NULL
из-за 7.1.2p5:
Любое определение объектоподобного макроса, описанное в этом разделе, должно расширяться до кода, который полностью защищенв скобках, где это необходимо, чтобы он группировался в произвольном выражении, как если бы это был один идентификатор.
Если NULL
развернуть до (void*)0
, то выражение sizeof NULL
будет синтаксисомошибка.
Так что насчет ((void*)0)
?Ну, я на 99,9% уверен, что намеревался быть допустимым определением для NULL
, но 6.5.1, который описывает выражения в скобках, говорит:
В скобкахвыражение является основным выражением.Его тип и значение идентичны типам выражения без скобок.Это lvalue, обозначение функции или выражение void, если выражение без скобок является, соответственно, lvalue, указателем функции или выражением void.
Это не говорит о том, что заключенная в скобки константа нулевого указателя является константой нулевого указателя.Тем не менее, насколько мне известно, все компиляторы C разумно полагают, что заключенная в скобки константа нулевого указателя является константой нулевого указателя, что делает ((void*)0
допустимым определением для NULL
.
Что если нулевой указатель представлен некак все биты-ноль, но как некоторый другой битовый шаблон, например, один эквивалент 0xFFFFFFFF
.Тогда (void*)0xFFFFFFFF
, даже если случается, что вычисление с нулевым указателем не является константой с нулевым указателем, просто потому, что оно не удовлетворяет определению этого термина.
Итак, какие другие вариации разрешены стандартом?
Поскольку реализации могут принимать другие формы константного выражения, компилятор может определить __null
как константное выражение типа int
со значением 0
, что позволяет либо __null
, либо ((void*)__null)
какопределение NULL
.Он может также сделать __null
самой константой типа указателя , но затем не сможет использовать __null
в качестве определения NULL
, поскольку он не удовлетворяет определению в 6.3.2.3.p3.
Компилятор может выполнить то же самое, не используя магию компилятора, например:
enum { __null };
#define NULL __null
Здесь __null
является целочисленным константным выражением типа int
со значением0
, поэтому его можно использовать везде, где можно использовать константу 0
.
Преимущество, определяющее NULL
в терминах такого символа, как __null
, заключается в том, что компилятор может затем выдатьнеобязательно) предупреждение, если в константе без указателя используется NULL
.Например, это:
char c = NULL; /* PLEASE DON'T DO THIS */
совершенно допустимо, если NULL
определено как 0
;расширение NULL
до некоторого распознаваемого токена, например __null
, упростит компилятору обнаружение этой сомнительной конструкции.