Как правильно писать код C/С++, когда нулевой указатель не все биты нуль

Как говорится в comp.lang.c FAQ, существуют архитектуры, где нулевой указатель не все биты нуль. Итак, вопрос в том, что на самом деле проверяет следующую конструкцию:

void* p = get_some_pointer();
if (!p)
    return;

Я сравниваю p с машинным нулевым указателем или я сравниваю p с арифметическим нулем?

Должен ли я писать

void* p = get_some_pointer();
if (NULL == p)
    return;

вместо этого быть готовым к таким архитектурам или это просто моя паранойя?

Ответ 1

В соответствии со спецификацией C:

Целочисленное постоянное выражение со значением 0 или такое выражение cast to type void *, называется константой нулевого указателя. 55) Если нулевой константа указателя преобразуется в тип указателя, в результате указатель, называемый нулевым указателем, гарантированно сравнится неравномерно с указатель на любой объект или функцию.

So 0 - константа нулевого указателя. И если мы преобразуем его в тип указателя, мы получим нулевой указатель, который может быть не-бит-бит для некоторых архитектур. Затем давайте посмотрим, что говорит спецификация сравнения указателей и константы нулевого указателя:

Если один операнд является указатель, а другой - константа нулевого указателя, нулевой указатель константа преобразуется в тип указателя.

Рассмотрим (p == 0): сначала 0 преобразуется в нулевой указатель, а затем p сравнивается с константой нулевого указателя, фактические значения бит которой зависят от архитектуры.

Далее, посмотрите, что говорит спецификация оператора отрицания:

Результат оператора логического отрицания! 0, если значение операнд сравнивается не равным 0, 1, если значение его операнда сравнивается равный 0. Результат имеет тип int. Выражение! E эквивалентно к (0 == E).

Это означает, что (!p) эквивалентно (p == 0), который, согласно спецификации, тестирует p на заданную машиной константу нулевого указателя.

Таким образом, вы можете спокойно писать if (!p) даже на архитектурах, где константа нулевого указателя не является битами-ноль.

Как и для С++, константа нулевого указателя определяется как:

Константа нулевого указателя является интегральным постоянным выражением (5.19) prvalue целочисленного типа, который оценивается в 0 или prvalue типа станд:: nullptr_t. Константа нулевого указателя может быть преобразована в указатель тип; результатом является значение нулевого указателя этого типа и отличаться от любого другого значения указателя или функции объекта тип указателя.

Это близко к тому, что у нас есть для C, плюс синтаксический сахар nullptr. Поведение оператора == определяется следующим образом:

Кроме того, можно сравнивать указатели на элементы или указатель на член и константу нулевого указателя. Преобразование указателей в члены (4.11) и квалификационные преобразования (4.4) выполняются для их приведения к общему типу. Если один операнд является константой нулевого указателя, общий тип - это тип другого операнда. В противном случае type - это указатель на тип члена, похожий (4.4) на тип одного из операнды с cv-квалификационной сигнатурой (4.4), которая является объединение cv-квалификационных сигнатур типов операндов. [ Заметка: это означает, что любой указатель на элемент можно сравнить с нулевым постоянная указателя. - конечная нота]

Это приводит к преобразованию 0 в тип указателя (как для C). Для оператора отрицания:

Операнд логического оператора отрицания! контекстуально преобразован в bool (раздел 4); его значение истинно, если преобразованный операнд истинно и false в противном случае. Тип результата - bool.

Это означает, что результат !p зависит от того, как выполняется преобразование из указателя в bool. В стандарте говорится:

Значение нуля, значение нулевого указателя или значение указателя нулевого элемента преобразован в false;

Так что if (p==NULL) и if (!p) тоже делают то же самое на С++.

Ответ 2

Не имеет значения, является ли нулевой указатель нулевым или нет в фактической машине. Предполагая, что p является указателем:

if (!p) 

всегда является законным способом проверить, является ли p нулевым указателем, и он всегда эквивалентен:

if (p == NULL)

Вам может быть интересна другая статья C-FAQ: Это странно. NULL гарантированно равен 0, но нулевой указатель не является?


Выше верно для C и С++. Обратите внимание, что в С++ (11) он предпочитает использовать nullptr для литерала с нулевым указателем.

Ответ 3

Этот ответ относится к C.

Не смешивайте NULL с нулевыми указателями. NULL - это просто макрос, гарантированный константой нулевого указателя. Константа нулевого указателя должна быть либо 0, либо (void*)0.

Из C11 6.3.2.3:

Целочисленное постоянное выражение со значением 0 или такое выражение cast to type void *, называется константой нулевого указателя 66). Если нулевой константа указателя преобразуется в тип указателя, в результате указатель, называемый нулевым указателем, гарантированно сравнится неравномерно с указатель на любой объект или функцию.

66) Макрос NULL определяется в < stddef.h > (и других заголовках) как константа нулевого указателя; см. 7.19.

7,19

Макросы

NULL

который расширяется до константы нулевого указателя, определяемой реализацией;

Реализация, определенная в случае NULL, равна либо 0, либо (void*)0. NULL больше не может быть.

Однако, когда константа нулевого указателя передается указателю, вы получаете нулевой указатель, который может не иметь нулевого значения, даже если он сравнивается с константой нулевого указателя. Код if (!p) не имеет ничего общего с макросом NULL, вы сравниваете нулевой указатель на нулевое арифметическое значение.

Таким образом, в теории код типа int* p = NULL может привести к нулевому указателю p, который отличается от нуля.

Ответ 4

В тот же день компьютеры STRATUS имели нулевые указатели как 1 на всех языках.

Это вызвало проблемы для C, поэтому их компилятор C разрешил сравнение указателей 0 и 1 с возвратом true

Это позволит:

void * ptr=some_func();
if (!ptr)
{
    return;
}

To return на нулевом ptr, хотя вы могли видеть, что ptr имеет значение 1 в отладчике

if ((void *)0 == (void *)1)
{
    printf("Welcome to STRATUS\n");
}

На самом деле напечатайте "Добро пожаловать в STRATUS"

Ответ 5

Если ваш компилятор хорош, есть две вещи (и только две вещи).

1: Статические инициализированные по умолчанию (то есть не назначенные) указатели не будут иметь NULL в них.

2: memset() в структуре или массиве или по вызову calloc() не будет указывать указатели на NULL.