Подтвердить что ты не робот

Может ли выражение "(ptr == 0)!= (Ptr == (void *) 0)" действительно верно?

Я прочитал эту претензию в тему форума, связанную с в комментарии от @jsantander:

Имейте в виду, что когда вы назначаете или сравниваете указатель на ноль, есть некоторая специальная магия, которая происходит за кулисами, чтобы использовать правильный шаблон для данного указателя (который может фактически не быть нулевым). Это одна из причин, почему такие вещи, как #define NULL (void*)0, являются злыми - если вы сравниваете char* с NULL, что магия явно (и, вероятно, неосознанно) отключена, и может произойти недопустимый результат. Просто чтобы быть более ясным:

(my_char_ptr == 0) != (my_char_ptr == (void*)0)

Итак, как я понимаю, для архитектуры, где указатель NULL, скажем, 0xffff, код if (ptr), сравнивает ptr с 0xffff, а не с 0.

Это правда? Описывается ли это стандартом С++?

Если true, это означает, что 0 можно безопасно использовать даже для архитектур, которые имеют ненулевое значение указателя NULL.

Edit

В качестве дополнительного пояснения рассмотрите этот код:

char *ptr;
memset(&ptr, 0, sizeof(ptr));
if ((ptr == (void*)0) && (ptr != 0)) {
    printf("It can happen.\n");
}

Вот как я понимаю утверждение этого сообщения на форуме.

4b9b3361

Ответ 1

На ваш вопрос две части. Я начну с:

Если true, это означает, что 0 можно безопасно использовать даже для архитектур, которые имеют ненулевое значение указателя NULL.

Вы смешиваете "значение" и "представление". Значение нулевого указателя называется значением нулевого указателя. Представление представляет собой бит в памяти, который используется для хранения этого значения. Представление нулевого указателя может быть любым, нет требования, чтобы он был битами-ноль.

В коде:

char *p = 0;

p гарантированно является нулевым указателем. Он может иметь не все бит-ноль.

Это больше не "волшебство", чем код:

float f = 5;

f не имеет того же представления (бит-паттерн в памяти), что и int 5, но проблем нет.

Этот стандарт определяет стандарт С++. Текст немного изменился в С++ 11 с добавлением nullptr; однако во всех версиях C и С++ целочисленный литерал 0 при преобразовании в тип указателя генерирует нулевой указатель.

Из С++ 11:

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

0 является константой нулевого указателя, а (char *)0, например, является значением нулевого указателя типа char *.

Не имеет значения, имеет ли нулевой указатель все бит-ноль или нет. Важно то, что нулевой указатель гарантированно генерируется при преобразовании целочисленного constexpr значения 0 в тип указателя.

Перемещение на другую часть вашего вопроса. Текст, который вы цитировали, является полным мусором. Там нет "магии" в идее, что преобразование между типами приводит к другому представлению, как я обсуждал выше.

Код my_char_ptr == NULL гарантированно проверяет, является ли my_char_ptr нулевым указателем.

Было бы плохо, если бы вы писали в своем собственном исходном коде, #define NULL (void*)0. Это связано с тем, что поведение undefined определяет любой макрос, который может быть определен стандартным заголовком.

Однако стандартные заголовки могут писать все, что им нравится, так как выполняются стандартные требования для нулевых указателей. Компиляторы могут "делать магию" в стандартном коде заголовка; например, в файловой системе не обязательно должен быть файл с именем iostream; компилятор может видеть #include <iostream>, а затем жестко закодировал всю информацию, которую Стандарт требует опубликовать iostream. Но по очевидным практическим причинам компиляторы обычно этого не делают; они позволяют возможность независимым командам разрабатывать стандартную библиотеку.

В любом случае, если компилятор С++ включает #define NULL (void *)0 в свой собственный заголовок, и в результате происходит что-то несоответствие, то компилятор явно не соответствует требованиям. И если ничего несоответствия не происходит, тогда нет проблем.

Я не знаю, кто текст, который вы цитируете, направить его комментарий "злой". Если он направлен на поставщиков компиляторов, говорящих им, чтобы они не были "злыми" и выставляли несоответствующие компиляторы, я думаю, мы не можем с этим спорить.

Ответ 2

Я думаю, что форум, на котором вы ссылаетесь, неверен (или мы неверно истолковали, что это означает !=). Два подвыражения имеют разную семантику, но тот же результат. Предполагая, что my_char_ptr действительно имеет тип char* или аналогичный, и допустимое значение:

my_char_ptr == 0 преобразует 0 в тип my_char_ptr. Это дает нулевой указатель, потому что 0 является примером так называемой "константы нулевого указателя", которая определена в стандарте. Затем он сравнивает эти два. Сравнение истинно тогда и только тогда, когда my_char_ptr является нулевым указателем, потому что только нулевые указатели сравниваются с другими нулевыми указателями.

my_char_ptr == (void*)0 преобразует my_char_ptr в void*, а затем сравнивает это с результатом преобразования 0 в void* (который является нулевым указателем). Сравнение истинно тогда и только тогда, когда my_char_ptr является нулевым указателем, потому что при преобразовании указателя в void* результат является нулевым указателем тогда и только тогда, когда источник является нулевым указателем.

Вопрос о том, представлены ли нулевые указатели с 0 битами или нет, интересен, но не имеет отношения к анализу кода.

Практическая опасность думать, что NULL является нулевым указателем (а не просто константой нулевого указателя), состоит в том, что вы можете думать, что printf("%p", NULL) определил поведение или что foo(NULL) вызовет перегрузку void* foo, а не перегрузка int и т.д.

Ответ 3

Нет, потому что они случайно использовали единственный случай, когда это гарантировано работает как пример.
В противном случае, да.

Хотя на практике вы, вероятно, никогда не увидите разницы, строго говоря, беспокойство верное.

Стандарт С++ требует (4.10), что:

  • Константа нулевого указателя (которая является либо интегральным константным выражением, которое оценивается как 0, либо значением класса std::nullptr_t) преобразуется в нулевой указатель любого типа.
  • Два нулевых указателя одинакового типа сравниваются.
  • Значение типа pointer-to-cv-T может быть преобразовано в pointer-to-cv-void, и значение нулевого указателя будет соответствующим образом скорректировано.
  • Указатели производных классов могут быть преобразованы в указатели базовых классов, и значение нулевого указателя будет соответствующим образом скорректировано.

Это означает, что если вы педантичны в формулировке, что нулевые указатели void и char и foo_bar не только не обязательно имеют нулевые битовые шаблоны, но и также не являются обязательно то же самое. Только нулевые указатели того же типа обязательно одинаковы (и, фактически, даже это не так, он говорит только, что они должны сравнивать равные, что не то же самое).

Тот факт, что он явно говорит: "Значение нулевого указателя преобразуется в значение нулевого указателя тип назначения "означает, что это не только абсурдный, теоретический изгиб формулировки, но и предназначен как законная особенность реализации.

Это независимо от того, что тот же литерал 0 преобразует в нулевой указатель каждого типа.

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

Ответ 4

Во-первых, я не уверен, что (charPtr == 0) != (charPtr == (void*)0) разрешен даже на С++. В обоих случаях вы преобразование константы нулевого указателя (0) в указатель, который приводит к нулевому указателю. И все нулевые указатели должны сравнивать равны.

Во-вторых, пока я не знаю контекста прохода, который вы цитируете, вам действительно не нужно беспокоиться о NULL (void*)0: пользовательский код не может юридически определить NULL (по крайней мере, если он включает в себя любые стандартные заголовки), а для стандарта С++ требуется NULL определяется как константа нулевого указателя; т.е. постоянное интегральное выражение, оценивающее значение 0. (Заметим, что несмотря на его имя, константа нулевого указателя не может иметь указатель type.) Таким образом, это может быть 0 (более или менее стандартный определение, начиная с самого начала С) или, возможно, 0L, или даже (1-1), но не ((void*)0). (Конечно, это могло бы также есть что-то вроде __nullptr, встроенная константа компилятора который вычисляет целое число 0, но вызывает предупреждение, если не преобразуется немедленно в нулевой указатель.

Наконец: нет требования, чтобы нулевой указатель имел все 0 бит, и, безусловно, были случаи, когда это не было случай. С другой стороны, существует требование, чтобы сравнение нулевого указателя с константой нулевого указателя будет оценивать значение true; это до компилятора, чтобы заставить его работать. А также поскольку NULL требуется определить как нулевой указатель постоянный, используете ли вы NULL или 0 чисто вопрос личные предпочтения и соглашения.

EDIT:

Просто немного уточнить: критическая точка включает преобразование "константы нулевого указателя", интегральное постоянное выражение оценивая 0. Что может удивить людей:

int zero = 0;       //  NOT a constant expression.
void* p1 = reinterpret_cast<void*>( zero );
void* p2 = 0;
if ( p1 == p2 )     //  NOT guaranteed!

Результаты преобразования не константного выражения, которое оценивает до нуля, чтобы указатель не был равен нулю указатель.