Является ли (int *)0 нулевым указателем?
Это можно рассматривать как расширение этого вопроса (меня интересует только C, но я добавляю C++ для завершения расширения)
Стандарт C11 в 6.3.2.3.3 гласит:
Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу
void *
, называется константой нулевого указателя.
Что я думаю об этом лично, так это 0
а также (void *)0
представляют нулевой указатель, целочисленное значение которого может фактически не быть 0, но это не покрывает приведение 0 к любому другому типу.
Но стандарт тогда продолжается:
Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем,...
который охватывает (int *)0
как нулевой указатель, поскольку приведение является явным преобразованием (C11, 6.3), которое указано в разделе методов преобразования.
Однако, что все еще заставляет меня задуматься, так это следующая фраза
... или такое выражение, приведенное к типу
void *
...
С вышеуказанной семантикой эта фраза кажется совершенно бесполезной. Вопрос в том, является ли эта фраза совершенно бесполезной? Если нет, какие последствия это имеет? Следовательно, (int *)0
нулевой указатель или нет?
Другой вопрос, который может помочь обсуждению, заключается в следующем. Является (long long)123
считается "123 преобразуется в long long
"или" 123 с типом long long
". Другими словами, есть ли преобразование в (long long)123
? Если нет, то вторая цитата выше не распространяется (int *)0
как нулевой указатель.
2 ответа
Короткий ответ:
И в C, и в C++ (int *)0
константное выражение, значение которого является нулевым указателем. Однако это не константа нулевого указателя. Единственное наблюдаемое различие между константным выражением, чье значение-это-нулевой указатель и константой нулевого указателя, о котором я знаю, состоит в том, что константу нулевого указателя можно присвоить l-значению любого тип указателя, но указатель-константа-выражение-значения-которого-ноль-указатель имеет определенный тип указателя и может быть назначен только lvalue с совместимым типом. В C, но не в C++, (void *)0
также является константой нулевого указателя; это особый случай для void *
в соответствии с общим правилом C-но-не-C++, что 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
и константное выражение-чье-значение-это-нулевой указатель с типом 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
"Неуказанные" сравнения не вызывают неопределенного поведения, но в стандарте не говорится, оценивают ли они значение "истина" или "ложь", и реализация не обязана документировать, какой из двух он является, или даже выбрать одно и придерживаться его. Это было бы совершенно справедливо для вышеизложенного memcmp
переключаться между возвратом 0 и 1, если вы вызывали его много раз.
Длинный ответ со стандартными кавычками:
Чтобы понять, что такое константа нулевого указателя, вы должны сначала понять, что такое выражение целочисленной константы, и это довольно странно - для полного понимания необходимо подробно прочитать разделы 6.5 и 6.6 C99. Это мое резюме:
Константное выражение - это любое C-выражение, которое компилятор может вычислить до константы, не зная значения какого-либо объекта (
const
или иным образом; тем не мение,enum
значения являются честной игрой), и которая не имеет побочных эффектов. (Это резкое упрощение примерно 25 страниц стандартного языка и может быть неточным.)Целочисленные константные выражения - это ограниченное подмножество константных выражений, удобно определяемое в одном абзаце, C99 6.6p6 и его сноска:
Целочисленное константное выражение96 должно иметь целочисленный тип и иметь только те операнды, которые являются целочисленными константами, константами перечисления, символьными константами,
sizeof
выражения, результатом которых являются целочисленные константы и плавающие константы, которые являются непосредственными операндами приведений. Операторы приведения в выражении с целочисленной константой должны преобразовывать только арифметические типы в целочисленные типы, кроме как как часть операнда вsizeof
оператор.96 Выражение из целочисленной константы используется для указания размера элемента битового поля структуры, значения константы перечисления, размера массива или значения константы регистра. Дополнительные ограничения, которые применяются к целочисленным константным выражениям, используемым в [
#if
] обсуждаются в 6.10.1.Для целей этого обсуждения важный бит
Операторы приведения... должны преобразовывать только арифметические типы в целочисленные типы
Который означает, что
(int *)0
не является целочисленным константным выражением, хотя это константное выражение.
Определение C++98 представляется более или менее эквивалентным, по модулю C++ функции и отклонения от C. Например, более сильное разделение символьных и логических типов от целочисленных типов в C++ означает, что стандарт C++ говорит о "интегральных константных выражениях" вместо "целочисленных константных выражений", а затем иногда требуется не просто целочисленное константное выражение, но целочисленное константное выражение целочисленного типа, исключая char
, wchar_t
, а также bool
(а может и signed char
а также unsigned char
? мне не понятно из текста).
Теперь, определение C99 константы нулевого указателя - вот о чем этот вопрос, поэтому я повторю это: 6.3.2.3p3 говорит
Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу
void *
, называется константой нулевого указателя. Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно сравнивается с неравным указателем на любой объект или функцию.
Стандарт очень, очень буквальный. Эти два предложения означают одно и то же:
Выражение из целочисленной константы со значением 0 называется константой нулевого указателя.
Целочисленное константное выражение со значением 0, приведенное к типуvoid *
также является константой нулевого указателя.
Когда любая константа нулевого указателя преобразуется в тип указателя, результирующий указатель называется нулевым указателем и гарантированно сравнивает неравные...
(Курсив - определение термина. Жирный шрифт - мой акцент.) Так что это означает, в C, (long *)0
а также (long *)(void *)0
два способа написания абсолютно одного и того же, а именно нулевого указателя с типом long *
,
С ++ отличается. Эквивалентный текст: C++98 4.10 [conv.ptr]:
Константа нулевого указателя - это целочисленное константное выражение (5.19) r целого типа, которое оценивается как ноль.
Это все. "Целочисленное константное выражение rvalue целочисленного типа" - это почти то же самое, что и "целочисленное константное выражение" в C99, но есть несколько вещей, которые соответствуют C, но не C++: например, в C символьный литерал '\x00'
является выражением целочисленной константы и, следовательно, константой нулевого указателя, но в C++ это не является выражением целочисленной константы целочисленного типа, поэтому она также не является константой нулевого указателя.
Более того, C++ не имеет "или такое выражение, приведенное к void *
"пункт. Это означает, что ((void *)0)
не является константой нулевого указателя в C++. Это все еще нулевой указатель, но он не совместим с любым другим типом указателя. Это согласуется с общепринятой системой типов C++.
C++ 11 (но не AFAIK, C11) пересмотрел концепцию "нулевого указателя", добавив для них специальный тип (nullptr_t
) и новое ключевое слово, которое оценивается как константа нулевого указателя (nullptr
). Я не до конца понимаю изменения и не буду пытаться их объяснить, но я уверен, что 0
по-прежнему является действительной константой нулевого указателя в C++11.
Оценивая выражение (int*)0
дает нулевой указатель типа int*
,
(int*)0
не является константой нулевого указателя.
Константа нулевого указателя - это особый вид выражения, которое может появляться в исходном коде языка Си. Пустой указатель - это значение, которое может встречаться в работающей программе.
C и C++ (будучи двумя разными языками) имеют немного разные правила в этой области. C++ не имеет "или такое выражение приведенное к типу void*
Формулировка. Но я не думаю, что это влияет на ответ на ваш вопрос.
Что касается вашего вопроса о (long long)123
Я не уверен, как это связано, но выражение 123
имеет тип int
и приведение определяет преобразование из int
в long long
,
Я думаю, что основная путаница заключается в предположении, что актеры в (int*)0
не указывает конверсию, так как 0
уже является константой нулевого указателя. Но константа нулевого указателя не обязательно является выражением типа указателя. В частности, выражение 0
является константой нулевого указателя и выражением типа int
; это не любой тип указателя. Термин константа нулевого указателя должен рассматриваться как единое понятие, а не фраза, значение которой зависит от отдельных слов, составляющих ее.