Является ли разыменование указателя, равного неопределенному поведению nullptr по стандарту?

автор блога поднял дискуссию о null dereferecing указатель:

Я привел здесь несколько контраргументов:

его основная линия рассуждений, цитирующих стандарт, такова:

выражение '&podhd->line6' является неопределенным поведением на языке C когда "podhd" является нулевым указателем.

стандарт C99 говорит следующее об адресе оператора'&': (6.5.3.2 "адреса и косвенного обращения операторов"):

операнд унарного оператора & должен быть либо функцией обозначение, результат [] или унарного * оператора или значение, которое обозначает объект, который не является битовым полем и не объявляется с спецификатор класса хранения регистра.

выражение 'podhd - >line6' явно не является функциональным обозначением, результат оператора [] или*. Это выражение lvalue. Однако, когда указатель "podhd" равен NULL, выражение не обозначает объект с 6.3.2.3 "указатели" говорит:

если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый указателем null, гарантированно сравнивать не равно указателю на какой-либо объект или функцию.

когда " lvalue не обозначает объект когда он выполняется, поведение не определено "(C99 6.3.2.1 "Lvalues, массивы и функции целеуказатели"):

lvalue-это выражение с типом объекта или неполным типом кроме void; если lvalue не обозначает объект, когда он оценено, поведение не определено.

Итак, та же идея вкратце:

когда -> был выполнен на указателе, он оценивался в lvalue, где объект не существует, и в результате поведение не определено.

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

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

максимум что я получаю это deferencing постоянной (или его приведение к любому типу указателя) - это то, что является UB, но ничего не говорит о переменной, которая бит равна значению, которое возникает из nullptr.

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

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

если вывод таков, если ptr равным значению nullptr разыменование это определенно UB, другой вопрос следует:

подразумевают ли стандарты C и C++, что специальное значение в адресном пространстве должно существовать только для представления значения нулевых указателей?

3 ответов


Как вы цитируете C, разыменование нулевого указателя явно неопределенное поведение из этой стандартной цитаты (акцент мой):

(C11, 6.5.3.2p4) " если указателю присвоено недопустимое значение, в поведение унарного оператора * не определено.102)"

102): "среди недопустимых значений для разыменования указателя унарным * оператором являются нулевой указатель, адрес, неправильно выровненный для типа об объекте, на который указывают, и об адресе объекта после окончания его срока службы."

точно такая же цитата В C99 и аналогичная в C89 / C90.


C++

dcl.ref / 5.

не должно быть ссылок на ссылки, массивов ссылок и указателей на ссылки. Этот объявление должно содержать инициализатор (8.5.3) за исключением случаев, когда декларация содержит явный спецификатор extern (7.1.1), является объявлением члена класса (9.2) в определении класса или объявлением параметра или возвращаемого типа (8.3.5); см. 3.1. Ссылка должна быть инициализируется для ссылки на допустимый объект или функция. [ Примечание: в частности, нулевая ссылка не может существовать в четко определенной программе, потому что единственный способ для создания такой ссылки было бы привязать ее к "объекту", полученному косвенным путем через нулевой указатель, что вызывает неопределенное поведение. как описано в 9.6, ссылка не может быть привязана непосредственно к бит-поля. - конец Примечания ]

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

Я уверен, что он говорит это где-то еще в более релевантном контексте, но этого достаточно.


ответ на это, который я вижу, относительно того, в какой степени нулевое значение может быть разыменовано, - это преднамеренно левая платформа-зависимая неопределенным образом из-за того, что осталось реализация-определенная в C11 6.3.2.3p5 и p6. Это в основном для поддержки автономных реализаций, используемых для разработки загрузочный код для платформы, как ОП указывает в своей ссылке опровержение, но и приложений тоже принимала реализации.

Re:
(C11, 6.5.3.2p4) " если недействительно указателю присвоено значение, поведение унарного оператора * не определено.102)"

102): "среди недопустимых значений для разыменования указателя унарным оператором * есть нулевой указатель, адрес, неправильно выровненный для типа объекта, на который указывает, и адрес объекта после окончания его жизни."

это сформулировано так, как есть, afaict, потому что каждый из случаев в сноске может быть недопустимым для определенных платформ компилятор таргетинг. Если там есть дефект, это "недопустимое значение" должно быть выделено курсивом и квалифицировано как "реализация". Для случая выравнивания платформа может иметь доступ к любому типу, используя любой адрес, поэтому не имеет требований к выравниванию, особенно если поддерживается опрокидывание адреса; и платформа может предположить, что время жизни объекта заканчивается только после выхода приложения, выделяя новый кадр через malloc() для автоматических переменных при каждом вызове функции.

на null указатели, во время загрузки платформа может иметь ожидания, что структуры, используемые процессором, имеют определенные физические адреса, в том числе по адресу 0, и представляются как указатели объектов в исходном коде, или может потребоваться функция, определяющая процесс загрузки, чтобы использовать базовый адрес 0. Если стандарт не разрешал разыменования типа '&podhd - >line6', где платформа требовала, чтобы podhd имел базовый адрес 0, то для доступа к этой структуре потребуется язык ассемблера. Аналогично, мягкий функция перезагрузки может потребоваться разыменовать 0-значный указатель в качестве вызова функции void. Размещенная реализация может считать 0 базой исполняемого образа и сопоставлять нулевой указатель в исходном коде с заголовком этого образа после загрузки, поскольку структура должна быть по логическому адресу 0 для этого экземпляра виртуальной машины C.

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

требование on (void *)0 больше того, что компилятор выдает код, который гарантирует выражения, где источник использует (void *)0, явно или путем ссылки на NULL, что фактическое значение, хранящееся, будет тем, которое говорит это не может указывать на какие-либо допустимые определения функций или объекты любым кодом сопоставления. Это не должно быть 0! Аналогично, для приведений (void *) от 0 до (obj_type) и (func_type) они требуются только для получения назначенных значений, которые оцениваются как адреса, гарантии компилятора не используются тогда для объектов или кода. Разница с последним заключается в том, что они не используются, а не недействительны, поэтому могут быть разыменованы определенным образом.

код, который проверяет равенство указателей затем проверит, является ли один операнд одним из этих значений, а другой-одним из 3, а не просто тем же битовым шаблоном, потому что это табло их с RTTI типа (null*), отличным от типов указателей void, obj и func для определенных объектов. Стандарт может быть более явным, это отдельный тип, если он не назван, потому что компиляторы используют его только внутри, но я полагаю, что это считается очевидным, когда "нулевой указатель" выделен курсивом. Эффективно, imo, "0" в этих contexts является дополнительным ключевым словом-маркером компилятора из-за дополнительного требования к нему, идентифицирующего тип (null*), но не характеризуется как таковой, потому что это усложнило бы определение .

Это сохраненное значение может быть SIZE_MAX так же легко, как 0, для (void *)0, в испущенном коде приложения, когда реализации, например, определяют диапазон от 0 до SIZE_MAX-4*sizeof(void *) дескрипторов виртуальной машины как то, что действительно для кода и данных. Этот Макрос NULL может быть даже определен как
(void *)SIZE_MAX, и компилятор должен был бы выяснить из контекста, что это имеет ту же семантику, что и 0. Код приведения отвечает за то, чтобы отметить, что это выбранное значение в указателе и указать, что подходит в качестве указателя объекта или функции. Приведения из Указателя целое число, неявное или явное, имеют аналогичные требования проверки и поставки; особенно в объединениях, где поле (u)intptr_t накладывает a (тип *) поле. Портативный код может защитить от компиляторов, которые не делают этого должным образом с явным *(ptr==NULL?(тип *) 0: ptr) выражение.