Как comp.lang.c FAQ говорит, что есть архитектуры, где нулевой указатель не все биты ноль. Так что вопрос в том, что на самом деле проверяет следующую конструкцию:
void* p = get_some_pointer();
if (!p)
return;
Я сравниваю p
с машинно-зависимым нулевым указателем или я сравниваю p
с арифметическим нулем?
Должен ли я написать
void* p = get_some_pointer();
if (NULL == p)
return;
вместо этого быть готовым к такой архитектуре или это просто моя паранойя?
Согласно спецификации C:
Целочисленное константное выражение со значением 0 или такое выражение
приведение к типу void *, называется константой нулевого указателя. 55) Если ноль
константа указателя преобразуется в тип указателя, в результате чего
указатель, называемый нулевым указателем, гарантированно сравнивает неравное с
указатель на любой объект или функцию.
Так 0
является константой нулевого указателя. И если мы преобразуем его в тип указателя, мы получим нулевой указатель, который может быть не-все-бит-ноль для некоторых архитектур. Далее давайте посмотрим, что спецификация говорит о сравнении указателей и константы нулевого указателя:
Если один операнд
указатель, а другой является константой нулевого указателя, нулевой указатель
константа преобразуется в тип указателя.
Давайте рассмотрим (p == 0)
: первый 0
преобразуется в нулевой указатель, а затем p
сравнивается с константой нулевого указателя, чьи фактические значения битов зависят от архитектуры.
Далее, посмотрите, что спецификация говорит об операторе отрицания:
Результат оператора логического отрицания! 0, если значение его
операнд сравнивает неравный с 0, 1, если значение его операнда сравнивается
равно 0. Результат имеет тип int. Выражение! E эквивалентно
к (0 == E).
Это означает, что (!p)
эквивалентно (p == 0)
что, согласно спецификации, тестирование p
против машинной константы нулевого указателя.
Таким образом, вы можете смело писать if (!p)
даже на архитектурах, где константа нулевого указателя не равна нулю.
Что касается C ++, константа нулевого указателя определяется как:
Константа нулевого указателя является интегральным константным выражением (5.19)
prvalue целочисленного типа с нулевым значением или prvalue типа
станд :: nullptr_t. Константа нулевого указателя может быть преобразована в указатель
тип; результатом является значение нулевого указателя этого типа и
отличается от любого другого значения указателя объекта или функции
тип указателя
Что близко к тому, что мы имеем для C, плюс nullptr
синтаксис сахар. Поведение оператора ==
определяется:
Кроме того, можно сравнивать указатели на элементы или указатель на
член и константа нулевого указателя. Указатель на членство преобразований
(4.11) и квалификационные преобразования (4.4) выполняются, чтобы привести их
к общему типу. Если один операнд является константой нулевого указателя,
общий тип — это тип другого операнда. В противном случае, общее
тип — указатель на тип члена, аналогичный (4.4) типу одного из
операнды с квалификационной подписью cv (4.4), которая является
объединение cv-квалификационных сигнатур типов операндов. [ Заметка:
это означает, что любой указатель на член можно сравнить с нулевым
константа указателя. — конец примечания]
Это приводит к преобразованию 0
к типу указателя (как для C). Для оператора отрицания:
Операнд оператора логического отрицания! контекстуально
преобразован в bool (пункт 4); его значение истинно, если преобразованный
операнд истинный и ложный в противном случае. Тип результата — bool.
Это означает, что результат !p
зависит от того, как преобразование из указателя на bool
выполняется. Стандарт гласит:
Нулевое значение, нулевое значение указателя или нулевое значение указателя элемента
преобразуется в ложь;
Так if (p==NULL)
а также if (!p)
делает то же самое и в C ++.
Неважно, является ли нулевой указатель нулем всех битов или нет на реальной машине. Если предположить, p
это указатель:
if (!p)
всегда легальный способ проверить, p
нулевой указатель, и он всегда эквивалентен:
if (p == NULL)
Вы можете быть заинтересованы в другой статье C-FAQ: Это странно. NULL гарантированно равен 0, но нулевой указатель — нет?
Выше верно как для C, так и для C ++. Обратите внимание, что в C ++ (11) предпочтительно использовать nullptr
для литерала нулевого указателя.
Этот ответ относится к C.
Не перепутай NULL
с нулевыми указателями. NULL
просто макрос гарантированно будет константа нулевого указателя. Константа нулевого указателя гарантированно будет либо 0
или же (void*)0
,
От С11 6.3.2.3:
Целочисленное константное выражение со значением 0 или такое выражение
приведение к типу void *, называется константой нулевого указателя 66). Если ноль
константа указателя преобразуется в тип указателя, в результате чего
указатель, называемый нулевым указателем, гарантированно сравнивает неравное с
указатель на любой объект или функцию.66) Макрос NULL определен в <stddef.h> (и другие заголовки) как константа нулевого указателя; см. 7.19.
7,19:
Макросы
НОЛЬ
которая расширяется до определенной в реализации постоянной нулевого указателя;
Реализация определяется в случае NULL
, либо 0
или же (void*)0
, NULL
не может быть ничего другого.
Однако, когда указателю присваивается константа с нулевым указателем, вы получаете нулевой указатель, который может не иметь значения ноль, даже если он сравнивается равным константе нулевого указателя. Код if (!p)
не имеет ничего общего с NULL
макрос, вы сравниваете нулевой указатель с арифметическим значением ноль.
Так что в теории, код как int* p = NULL
может привести к нулевому указателю p
который отличается от нуля.
В свое время компьютеры STRATUS имели нулевые указатели как 1 на всех языках.
Это вызвало проблемы для C, поэтому их компилятор C позволил бы при сравнении указателей 0 и 1 возвращать true
Это позволило бы:
void * ptr=some_func();
if (!ptr)
{
return;
}
к return
на нулевом ptr, хотя вы могли видеть, что ptr
имел значение 1 в отладчике
if ((void *)0 == (void *)1)
{
printf("Welcome to STRATUS\n");
}
Будет ли на самом деле печатать «Добро пожаловать в СТРАТУС»
Если ваш компилятор хорош, есть две вещи (и только две), на которые нужно обратить внимание.
1: статические по умолчанию инициализированные (то есть не назначенные) указатели не будут иметь NULL в них.
2: memset () для структуры или массива или расширения calloc () не будет устанавливать указатели в NULL.