Является ли 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.