Подтвердить что ты не робот

Is (int *) 0 нулевой указатель?

Это можно рассматривать как расширение для этого вопроса (меня интересует только C, но добавление С++ для завершения расширения)

В стандарте C11 в пункте 6.3.2.3.3 говорится:

Целочисленное константное выражение со значением 0 или выражением, выражаемым типом void *, называется константой нулевого указателя.

То, что я считаю лично, это то, что 0 и (void *)0 представляют нулевой указатель, чье целочисленное значение может не быть 0, но это не распространяется на 0, отличное от любого другого типа.

Но стандарт продолжает:

Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем,...

который покрывает (int *)0 как нулевой указатель, поскольку cast - это явное преобразование (C11, 6.3), которое указано в методах преобразования.

Однако меня удивляет следующая фраза

... или такое выражение, которое применяется к типу void *...

С этой семантикой эта фраза кажется совершенно бесполезной. Вопрос в том, является ли эта фраза совершенно бесполезной? Если нет, какие последствия у него есть? Следовательно, есть (int *)0 нулевой указатель или нет?


Другой вопрос, который может помочь в обсуждении, следующий. Рассматривается ли (long long)123 "123 преобразован в long long" или "123 с типом long long". Другими словами, есть ли какое-либо преобразование в (long long)123? Если их нет, то вторая цитата не охватывает (int *)0 как нулевой указатель.

4b9b3361

Ответ 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.

Ответ 2

Оценка выражения (int*)0 дает нулевой указатель типа int*.

(int*)0 не является константой нулевого указателя.

Константа нулевого указателя - это особый вид выражения, который может отображаться в исходном коде C. Нулевой указатель - это значение, которое может иметь место в запущенной программе.

C и С++ (два разных языка) имеют несколько разные правила в этой области. С++ не имеет выражения типа "или такое выражение, которое выдает тип void*". Но я не думаю, что это влияет на ответ на ваш вопрос.

Что касается вашего вопроса о (long long)123, я не уверен, как это связано, но выражение 123 имеет тип int, а cast указывает преобразование от int до long long.

Я думаю, что основная путаница - это предположение, что в составе (int*)0 не указано преобразование, так как 0 уже является константой нулевого указателя. Но константа нулевого указателя необязательно является выражением типа указателя. В частности, выражение 0 является как константой нулевого указателя, так и выражением типа int; он не имеет никакого типа указателя. Термин константа нулевого указателя следует рассматривать как единую концепцию, а не фразу, смысл которой зависит от отдельных слов, которые ее составляют.