Является ли NULL всегда равным нулю в C?
Вчера я беседовал с парнем для разработки программного обеспечения среднего уровня, и он упомянул, что в C NULL не всегда равен нулю и что он видел реализации C, где NULL не равен нулю. Я нахожу это очень подозрительным, но я хочу быть уверенным. Кто-нибудь знает, прав ли он?
(Ответы не повлияют на мое мнение по этому кандидату, я уже представил свое решение моему менеджеру.)
Ответы
Ответ 1
Я предполагаю, что вы имеете в виду нулевой указатель. Можно гарантировать, что оно сравнивается с 0
. 1 Но оно не обязательно должно быть представлено всеми нулевыми битами. 2
См. также comp.lang.c FAQ по нулевым указателям.
<Суб > - См. C99, 6.3.2.3.
- Нет явного требования; но см. сноску для C99, 7.20.3 (спасибо @birryree в комментариях).
Суб >
Ответ 2
В разделе 6.3.2.3 стандарта C99 говорится:
Целочисленное константное выражение со значением 0 или такое выражение, отлитое для типа void *, называется константой нулевого указателя). Если константа нулевого указателя преобразуется в тип указателя, полученный указатель, называемый нулевым указателем, гарантированно сравнивает неравные к указателю на любой объект или функцию.
В § 7.17 также говорится, что
[...] NULL, который расширяется до константы нулевого указателя, определяемой реализацией [...]
Адрес указателя NULL может отличаться от 0, тогда как он будет вести себя так, как это было в большинстве случаев.
(Это должно быть таким же, как в старых стандартах C, которых у меня сейчас нет)
Ответ 3
Константа нулевого указателя всегда равна 0. Макрос NULL
может быть определен реализацией как голый 0
или выражение для выражения, подобное (void *) 0
, или какое-либо другое нулевое целочисленное выражение (следовательно, реализация определяет "язык в стандарте".
Значение нулевого указателя может быть чем-то иным, чем 0. Когда встречается константа нулевого указателя, она будет преобразована в значение нулевого указателя.
Ответ 4
В C существует один и только один контекст, где необходимо явно использовать константу нулевого указателя для определенного типа указателя, чтобы программа работала правильно. Этот контекст передает нулевой указатель через список аргументов нетипизированной функции. В современном C это происходит только тогда, когда вам нужно передать нулевой указатель на функцию, которая принимает переменное количество аргументов. (В наследии 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
Да, этот последний пример имеет поведение undefined, даже если NULL
определяется как ((void *)0)
, потому что void *
и char *
неявно взаимно обратимы при передаче через список нетипизированных аргументов, хотя они везде иначе.
"Под капотом" проблема здесь не только с битовой диаграммой, используемой для нулевого указателя, но для того, чтобы правильно настроить кадр вызова, компилятору может понадобиться конкретный конкретный тип каждого аргумента. (Рассмотрим MC68000 с отдельными адресами и регистрами данных, некоторые ABI указали аргументы указателя, которые должны быть переданы в адресных регистрах, но целочисленные аргументы в регистрах данных. Рассмотрим также любой ABI, где int
и void *
не имеют одинакового размера. он исчезает редко в наши дни, но C явно указывает, что void *
и char *
не имеют одинакового размера.) Если есть прототип функции, компилятор может это использовать, но непрототируемые функции и вариативные аргументы не предлагают такой помощи.
С++ сложнее, и я не чувствую себя способным объяснить, как это сделать.
Ответ 5
В некоторых реализациях размер указателя не совпадает с размером целого числа. NULL в целочисленном контексте равен 0, но фактическая двоичная разметка не обязательно должна быть все 0.