Почему разыменование нулевого указателя является неопределенным поведением? - PullRequest
22 голосов
/ 22 июля 2011

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

Ответы [ 12 ]

37 голосов
/ 22 июля 2011

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

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

23 голосов
/ 22 июля 2011

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

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

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

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

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

11 голосов
/ 22 июля 2011

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

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

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

8 голосов
/ 22 июля 2011

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


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

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

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


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

5 голосов
/ 22 июля 2011

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

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

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

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

1 голос
/ 22 июля 2011

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

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

1 голос
/ 22 июля 2011

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

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

0 голосов
/ 28 мая 2015

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

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

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

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

0 голосов
/ 23 июля 2011

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

0 голосов
/ 23 июля 2011

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

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

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

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

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

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

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