Ответ 1
Короткий ответ:
В C и С++ (int *)0
- это постоянное выражение, значение которого является нулевым указателем. Однако он не является константой нулевого указателя. Единственное наблюдаемое различие между константой-выражением, чье значение-is-a-null-pointer и константой-указателем-указателем, о которой я знаю, является то, что константу-указатель можно присвоить l-значению любого тип указателя, но константа-выражение, чей-значение-is-a-null-pointer имеет определенный тип указателя и может быть назначен только для lvalue с совместимым типом. В C, но не С++, (void *)0
также является константой нулевого указателя; это особый случай для void *
, совместимый с общим правилом C-but-not-С++, который void *
присваивает совместимость с любым другим типом-указателем-объектом.
Например:
long *a = 0; // ok, 0 is a null pointer constant
long *b = (long *)0; // ok, (long *)0 is a null pointer with appropriate type
long *c = (void *)0; // ok in C, invalid conversion in C++
long *d = (int *)0; // invalid conversion in both C and C++
И здесь случай, когда разница между константой нулевого указателя (void *)0
и константным выражением, чье значение-is-a-null-pointer с типом void *
видимо, даже в C:
typedef void (*fp)(void); // any pointer-to-function type will show this effect
fp a = 0; // ok, null pointer constant
fp b = (void *)0; // ok in C, invalid conversion in C++
fp c = (void *)(void *)0; // invalid conversion in both C and C++
Кроме того, он сейчас обсуждается, но с тех пор, как вы его вынесли: независимо от того, что представляет собой бит-представление long *
нулевого указателя, все эти утверждения ведут себя, как указано комментариями:
// 'x' is initialized to a null pointer
long *x = 0;
// 'y' is initialized to all-bits-zero, which may or may not be the
// representation of a null pointer; moreover, it might be a "trap
// representation", UB even to access
long *y;
memset(&y, 0, sizeof y);
assert (x == 0); // must succeed
assert (x == (long *)0); // must succeed
assert (x == (void *)0); // must succeed in C, unspecified in C++
assert (x == (int *)0); // must succeed in C, unspecified in C++
assert (memcmp(&x, &y, sizeof y) == 0); // unspecified
assert (y == 0); // UNDEFINED BEHAVIOR: y may be a trap representation
assert (y == x); // UNDEFINED BEHAVIOR: y may be a trap representation
"Неуказанные" сравнения не провоцируют поведение undefined, но стандарт не говорит, оценивают ли они истину или ложь, и для реализации не требуется документировать, какая из двух она есть, или даже выбрать один и придерживаться его. Было бы совершенно правильно, если предыдущий memcmp
чередовался между возвратом 0 и 1, если вы его вызывали много раз.
Длинный ответ со стандартными кавычками:
Чтобы понять, что такое константа нулевого указателя, вам сначала нужно понять, что такое целочисленное постоянное выражение, и что довольно волосатое - полное понимание требует, чтобы вы подробно прочитали разделы 6.5 и 6.6 C99. Это мое резюме:
-
Постоянное выражение представляет собой любое выражение C, которое компилятор может оценить константе, не зная значения какого-либо объекта (
const
или иначе, однако значенияenum
являются честной игрой), и которые не имеют побочные эффекты. (Это радикальное упрощение примерно 25 страниц стандартного и может быть неточным.) -
Целочисленные константные выражения представляют собой ограниченное подмножество константных выражений, удобно определяемое в одном абзаце, C99 6.6p6 и его сноске:
Целочисленное константное выражение 96 должно иметь целочисленный тип и должно иметь только операнды, которые являются целыми константами, константами перечисления, символьными константами,
sizeof
выражениями, результаты которых являются целыми константами, и плавающие константы, которые являются непосредственные операнды отливок. Операторы Cast в целочисленном постоянном выражении должны преобразовывать только арифметические типы в целые типы, за исключением того, что часть операнда дляsizeof
Оператор.96 Целочисленное константное выражение используется для указания размера элемента битового поля структуры, значения константы перечисления, размера массива или значения аргумента постоянная. Дальнейшие ограничения, которые применяются к целочисленным константным выражениям, используемым в [
#if
], обсуждаются в 6.10.1.Для этого обсуждения важный бит
Операторы роли... должны преобразовывать только арифметические типы в целые типы
что означает, что
(int *)0
не является целочисленным постоянным выражением, хотя оно является константным выражением.
Определение С++ 98 выглядит более или менее эквивалентным, по модулю С++ и отклонениям от C. Например, более сильное разделение символьных и логических типов из целых типов в С++ означает, что стандарт С++ говорит о "интеграле константные выражения", а не "целочисленные константные выражения", а затем иногда требуется не просто интегральное постоянное выражение, а целочисленное постоянное выражение целочисленного типа, исключая char
, wchar_t
и bool
(и, возможно, также signed char
и unsigned char
? это не ясно мне из текста).
Теперь определение константы нулевого указателя C99 - это вопрос, о котором идет речь, поэтому я повторю его: 6.3.2.3p3 говорит
Целочисленное константное выражение со значением 0 или такое выражение, отлитое для типа
void *
, называется константой нулевого указателя. Если константа нулевого указателя преобразуется в тип указателя, полученный указатель, называемый нулевым указателем, гарантированно сравнится неравномерно с указателем на любой объект или функцию.
Стандарт очень, очень буквальный. Эти два предложения означают то же самое, что:
Целочисленное константное выражение со значением 0 называется константой нулевого указателя.
Целочисленное константное выражение со значением 0, отличное от типаvoid *
, является также константой нулевого указателя.
Когда любая константа нулевого указателя преобразуется в тип указателя, полученный указатель называется нулевым указателем и гарантированно сравнивает неравные...
(Курсив - определение термина. Boldface - мой акцент.) Итак, что это значит, в C, (long *)0
и (long *)(void *)0
есть два способа написания точно такой же вещи, а именно нулевой указатель с типом long *
.
С++ отличается. Эквивалентным текстом является С++ 98 4.10 [conv.ptr]:
Константа нулевого указателя является интегральным постоянным выражением (5.19) rvalue целочисленного типа, который вычисляется до нуля.
Это все. "Интегральное постоянное выражение rvalue целочисленного типа" почти такое же, как C99 "целочисленное постоянное выражение", но есть несколько вещей, которые можно квалифицировать в C, но не С++: например, в C символьный литерал '\x00'
является целочисленное постоянное выражение и, следовательно, константу нулевого указателя, но в С++ он не является интегральным постоянным выражением целочисленного типа, поэтому он также не является константой нулевого указателя.
Более того, С++ не имеет предложения "или такое выражение, отличное от void *
". Это означает, что ((void *)0)
является not константой нулевого указателя в С++. Он по-прежнему является нулевым указателем, но не является присвоением, совместимым с любым другим типом указателя. Это согласуется с системой С++ обычно типа pickier.
С++ 11 (но не, AFAIK, C11) пересмотрел концепцию "нулевого указателя", добавив для них специальный тип (nullptr_t
) и новое ключевое слово, которое оценивает константу нулевого указателя (nullptr
). Я не совсем понимаю изменения и не собираюсь их объяснять, но я уверен, что голый 0
по-прежнему является допустимой константой нулевого указателя в С++ 11.