Как правильно написать код C/C++, когда нулевой указатель не все биты ноль
Как говорится в FAQ по comp.lang.c, существуют архитектуры, в которых нулевой указатель не равен нулю. Так что вопрос в том, что на самом деле проверяет следующую конструкцию:
void* p = get_some_pointer();
if (!p)
return;
Я сравниваю p
с машинно-зависимым нулевым указателем или я сравниваю p
с арифметическим нулем?
Должен ли я написать
void* p = get_some_pointer();
if (NULL == p)
return;
вместо этого быть готовым к такой архитектуре или это просто моя паранойя?
5 ответов
Согласно спецификации 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 типа std::nullptr_t. Константа нулевого указателя может быть преобразована в тип указателя; результат является нулевым значением указателя этого типа и отличается от любого другого значения указателя объекта или типа указателя функции.
Что близко к тому, что мы имеем для C, плюс nullptr
синтаксис сахар. Поведение оператора ==
определяется:
Кроме того, можно сравнивать указатели на члены или указатель на член и константу нулевого указателя. Преобразования указателей в члены (4.11) и квалификационные преобразования (4.4) выполняются, чтобы привести их к общему типу. Если один операнд является константой нулевого указателя, общий тип является типом другого операнда. В противном случае общий тип - это указатель на тип члена, аналогичный (4.4) типу одного из операндов, с сигнатурой cv-квалификации (4.4), которая является объединением сигнатур cv-квалификации типов операндов. [Примечание: это означает, что любой указатель на член можно сравнить с константой нулевого указателя. - конец примечания]
Это приводит к преобразованию 0
к типу указателя (как для C). Для оператора отрицания:
Операнд оператора логического отрицания! контекстно преобразуется в bool (пункт 4); его значение равно true, если преобразованный операнд имеет значение true, в противном случае - false. Тип результата - 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 определен в
(и других заголовках) как константа нулевого указателя; см. 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.