Нуль всегда равен нулю в C?
вчера я брал интервью у парня на должность программиста среднего уровня, и он упомянул, что в C NULL не всегда равен нулю и что он видел реализации C, где NULL не равен нулю. Я нахожу это весьма подозрительным, но я хочу быть уверенным. Кто-нибудь знает, если он прав?
(ответы не повлияют на мое мнение об этом кандидате, я уже представил свое решение моему менеджеру.)
5 ответов:
Я предполагаю, что вы имеете в виду нулевой указатель. Он гарантированно сравнивается равным
0
.1 но он не должен быть представлен со всеми нулевыми битами.2см. также comp.ленг.с чаво на нулевые указатели.
- Посмотреть С99, 6.3.2.3.
- нет явного утверждения; но см. сноску для C99, 7.20.3 (благодаря @birryree в Примечание.)
§ 6.3.2.3 стандарта C99 говорит
целочисленное константное выражение со значением 0, или такое выражение приводится к типу типу Void *, называется нулевой указатель константа), если нулевой указатель константа преобразуется к типу указателя, результирующий указатель, называемый указателем null, гарантированно сравнивать неравные к указателю на любой объект или функцию.
§ 7.17 также говорит
[...] Нуль, который раскрывается в реализация-определенная константа нулевого указателя [...]
адрес нулевого указателя может отличаться от 0, в то время как он будет вести себя так, как это было в большинстве случаев.
(Это должно быть то же самое, что и в старых стандартах C, которых у меня сейчас нет)
нулевой указатель постоянный всегда 0. Элемент
NULL
макрос может быть определен реализацией как голый0
, или выражение приведения типа(void *) 0
, или какое-либо другое целочисленное выражение с нулевым значением (следовательно, язык "реализация определена" в стандарте).нулевой указатель стоимостью может быть что-то другое, чем 0. При обнаружении константы нулевого указателя она будет преобразована в соответствующее значение нулевого указателя.
в C существует один и только один контекст, в котором необходимо явно привести константу нулевого указателя к определенному типу указателя, чтобы программа работала правильно. Этот контекст передает нулевой указатель через нетипизированный список аргументов функции. В современные С, это происходит только тогда, когда вам нужно передать указатель на функцию, которая принимает переменное число аргументов. (В legacy C это происходит с любой функцией, не объявленной с прототипом.) Парадигматический пример -
execl
, где самый последний аргумент должен быть нулевым указателем, явно приведенным к(char *)
:execl("/bin/ls", "ls", "-l", (char *)0); // correct execl("/bin/ls", "ls", "-l", (char *)NULL); // correct, but unnecessarily verbose execl("/bin/ls", "ls", "-l", 0); // undefined behavior execl("/bin/ls", "ls", "-l", NULL); // ALSO undefined behavior
Да, вот последний пример имеет неопределенное поведение даже если
NULL
определяется как((void *)0)
, потому чтоvoid *
иchar *
are не неявно взаимопревращаемый при передаче через нетипизированный список аргументов, даже если они находятся везде."под капотом", проблема здесь не только с битовым шаблоном, используемым для нулевого указателя, но компилятору может потребоваться знать точный конкретный тип каждого аргумента, чтобы правильно настроить фрейм вызова. (Рассмотрим MC68000 с его отдельными регистрами адресов и данных; некоторые abis задают аргументы указателя, которые должны передаваться в регистрах адресов, но целочисленные аргументы в регистрах данных. Рассмотрим также любой ABI, где
int
иvoid *
не тот же размер. И это исчезающе редко в наши дни, но C по-прежнему явно предусматриваетvoid *
иchar *
не тот же размер.) Если есть прототип функции, компилятор может использовать это, но незащищенные функции и вариативные аргументы не предлагают такой помощи.C++ сложнее, и я не чувствую себя квалифицированным, чтобы объяснить, как это сделать.