Как правильно написать код 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.

Другие вопросы по тегам