Нуль всегда равен нулю в C?


вчера я брал интервью у парня на должность программиста среднего уровня, и он упомянул, что в C NULL не всегда равен нулю и что он видел реализации C, где NULL не равен нулю. Я нахожу это весьма подозрительным, но я хочу быть уверенным. Кто-нибудь знает, если он прав?

(ответы не повлияют на мое мнение об этом кандидате, я уже представил свое решение моему менеджеру.)

5 52

5 ответов:

Я предполагаю, что вы имеете в виду нулевой указатель. Он гарантированно сравнивается равным 0.1 но он не должен быть представлен со всеми нулевыми битами.2

см. также comp.ленг.с чаво на нулевые указатели.


  1. Посмотреть С99, 6.3.2.3.
  2. нет явного утверждения; но см. сноску для 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++ сложнее, и я не чувствую себя квалифицированным, чтобы объяснить, как это сделать.

Он прав, в некоторых реализациях, размер указателя не совпадает с размером целого числа. NULL в целочисленном контексте равен 0, но фактический двоичный макет не должен быть все 0s.