Почему разыменование нулевого указателя является неопределенным поведением?

согласно ISO C++, разыменование нулевого указателя является неопределенным поведением. Мое любопытство, почему? Почему standard решил объявить его неопределенным поведением? Чем обосновано это решение? Зависимость компилятора? Не похоже, потому что согласно стандарту C99, насколько я знаю, он хорошо определен. Машинная зависимость? Есть идеи?

12 ответов


определение согласованного поведения для разыменования нулевого указателя потребует от компилятора проверки нулевых указателей перед каждым разыменованием на большинстве архитектур ЦП. Это неприемлемый burdern для языка, который предназначен для скорости.

Он также исправляет только небольшую часть большей проблемы - есть много способов иметь недопустимый указатель за нулевым указателем.


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

на PDP-11 случилось так, что адрес 0 всегда содержал значение 0, поэтому разыменование нулевого указателя также дало значение 0. Довольно много людей, которые использовали эти машины, чувствовали, что, поскольку они были оригинальной машиной C, были написаны / использованы для программирования, что это следует считать каноническим поведением для C на всех машинах (хотя изначально это произошло совершенно случайно).

на некоторых других машинах (Interdata приходит на ум, хотя моя память может легко ошибаться) адрес 0 был использован в обычном режиме, поэтому он может содержать другие значения. Было также некоторое оборудование, на котором адрес 0 был на самом деле некоторым оборудованием, сопоставленным с памятью, поэтому чтение/запись он делал специальные вещи-совсем не эквивалентные чтению/записи нормальной памяти вообще.

лагеря не договорились о том, что должно произойти, поэтому они сделали это неопределенным поведением.

Edit: я полагаю, я должен добавить, что к тому времени, когда написал стандарт C++, его неопределенное поведение уже было хорошо установлено в C, и (по-видимому) никто не думал, что есть веская причина для создания конфликта по этому вопросу, поэтому они сохранили то же самое.


единственный способ дать определенное поведение - добавить проверку времени выполнения к каждому разыменованию указателя и каждой арифметической операции указателя. В некоторых ситуациях эти накладные расходы были бы неприемлемы и сделали бы C++ непригодным для высокопроизводительных приложений, для которых он часто используется.

C++ позволяет создавать собственные типы интеллектуальных указателей (или использовать те, которые поставляются библиотеками), которые могут включать такую проверку в случаях, когда безопасность важнее, чем спектакль.

разыменование нулевого указателя также не определено в C в соответствии с пунктом 6.5.3.2 / 4 стандарта C99.


этой ответ от @Johannes Schaub-litb, выдвигает интересное обоснование, которое кажется довольно убедительным.


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

еще одна проблема, которая добавляет путаницы заключается в том, что семантика typeid оператор делает часть этого страдания хорошо определенным. Он говорит, что если ему было дано значение lvalue, которое явилось результатом разыменования нулевого указателя, результат бросает bad_typeid исключения. Хотя, это ограниченная область, где существует исключение (без каламбура) к вышеуказанной проблеме поиска идентичности. Существуют и другие случаи, когда делается аналогичное исключение из неопределенного поведения (хотя и гораздо менее тонкое и со ссылкой на затронутые разделы).

комитет обсудил, как решить эту проблему глобально, определив вид lvalue, который не имеет идентификатора объекта или функции: так называемый пустой lvalue. У этой концепции, однако, все еще были проблемы, и они решили не принимать это.


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


вопрос в том, какое поведение вы ожидаете ?

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

Итак, как вы получаете хорошую ссылку... от указателя, который указывает в пустоту ?

нет. Таким образом неопределенное поведение.


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

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


иногда нужно недопустимый указатель (см. MmBadPointer в Windows), чтобы представить "ничего".

Если бы все было действительно, то это было бы невозможно. Так они сделали NULL недействительно и запрещает вам разыменовать его.


по оригинальным стандарт C NULL может быть любым значением -Не обязательно ноль.

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

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

(от http://c-faq.com/null/null1.html)


вот простой тест и пример:

  1. выделить указатель:

    тип int * указатель;

? Какое значение находится в указателе при его создании?
? На что указывает указатель?
? Что происходит, когда я разыменовать этот момент в его нынешнем состоянии?

  1. конец связанного списка. В связанном списке, узел указывает на другой узел, за исключением последний.
    каково значение указателя в последнем узле?
    Что происходит, когда вы разыграете поле "next" последнего узла?

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


хотя разыменование нулевого указателя в C / C++ действительно приводит к неопределенному поведению с точки зрения языка, такая операция хорошо определена в компиляторах для целей, которые имеют память по соответствующему адресу. В этом случае результат такой операции заключается в простом считывании памяти по адресу 0.

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

#define offsetof(st, m) ((size_t)(&((st *)0)->m))

было даже обсуждение сделать это поведение частью стандарта.


потому что вы не можете создать пустую ссылку. C++ этого не позволяет. Поэтому нельзя разыменовать нулевой указатель.

в основном это не определено, потому что нет логического способа справиться с этим.


вы можете разыменовать нулевой указатель. Кто-то сделал это здесь:http://www.codeproject.com/KB/system/soviet_kernel_hack.aspx