Ответ 1
TL; DR. Ваш пример четко определен. Простое разыменование нулевого указателя не вызывает UB.
Существует много споров по этой теме, которая в основном сводится к тому, является ли косвенным путем нулевой указатель сам UB.
Единственное сомнительное, что происходит в вашем примере, - это оценка выражения объекта. В частности, d->a
эквивалентно (*d).a
в соответствии с [expr.ref]/2:
Выражение
E1->E2
преобразуется в эквивалентную форму(*(E1)).E2
; оставшаяся часть 5.2.5 будет касаться только первой (точка).
*d
оценивается только:
Постфиксное выражение перед точкой или стрелкой оценивается: 65результат этой оценки вместе с id-выражением определяет результат всего постфиксного выражения.
65) Если выражение доступа к члену класса оценивается, оценка подвыражения выполняется, даже если результат не нужен для определения значения всего постфиксного выражения, например, если id-выражение обозначает статический член.
Позвольте извлечь критическую часть кода. Рассмотрим оператор выражения
*d;
В этом выражении *d
- это отброшенное выражение в соответствии с [stmt.expr]. Итак, *d
оценивается только 1 как и в d->a
.
Следовательно, если *d;
действительно, или, другими словами, оценка выражения *d
, так и ваш пример.
Оказывает ли косвенное обращение с помощью нулевых указателей поведение undefined?
Существует открытая проблема CWG # 232, созданная более пятнадцати лет назад, что касается этого точного вопроса. Возникает очень важный аргумент. Отчет начинается с
По крайней мере, несколько мест в состоянии IS, которые направляют через null pointer производит поведение undefined: 1.9 [intro.execution] в пункте 4 дается "разыменование нулевого указателя" в качестве примера undefined и 8.3.2 [dcl.ref], пункт 4 (в примечании) использует это предположительно undefined поведение как оправдание для несуществование "нулевых ссылок".
Обратите внимание, что упомянутый пример был изменен для изменения модификаций объектов const
, а примечание в [dcl.ref] - пока все еще существует - не является нормативным. Нормативный проход был удален, чтобы избежать обязательств.
Однако, пункт 5.3.1 [expr.unary.op], который описывает унарный "
*
", не говорит, что поведение undefined, если операнд - нулевой указатель, как и следовало ожидать. Кроме того, по крайней мере один проход дает разыменование корректного поведения нулевого указателя: 5.2.8 [expr.typeid], пункт 2, говоритЕсли выражение lvalue получается путем применения унарного * оператора указателю, а указатель - нулевым значением указателя (4.10 [conv.ptr]), выражение typeid вызывает исключение bad_typeid (18.7.3 [bad.typeid]).
Это несовместимо и должно быть очищено.
Последний пункт особенно важен. Цитата в [expr.typeid] все еще существует и содержит glvalues типа полиморфного класса, что имеет место в следующем примере:
int main() try {
// Polymorphic type
class A
{
virtual ~A(){}
};
typeid( *((A*)0) );
}
catch (std::bad_typeid)
{
std::cerr << "bad_exception\n";
}
Поведение этой программы хорошо определено (исключение будет выбрано и выхвачено), а выражение *((A*)0)
оценивается, поскольку оно не является частью неоцененного операнда. Теперь, если косвенность через нулевые указатели индуцировала UB, тогда выражение, записанное как
*((A*)0);
будет делать именно это, вызывая UB, что кажется бессмысленным по сравнению с сценарием typeid
. Если вышеприведенное выражение просто оценивается, поскольку каждое выражение с отброшенным значением является 1 где критическое различие, которое делает оценку во втором фрагменте UB? Нет существующей реализации который анализирует typeid
-operand, находит самую внутреннюю, соответствующую разыменованность и окружает ее операнд с проверкой - также будет потеря производительности.
Затем в этом выпуске заканчивается короткое обсуждение:
Мы согласились, что подход в стандарте выглядит нормально:
p = 0; *p;
не является по своей сути ошибкой. Преобразование lvalue-to-rval даст это поведение undefined.
т.е. комитет согласился с этим. Хотя предлагаемая резолюция этого доклада, в которой вводились так называемые "пустые слова", никогда не принималась...
Однако "не модифицируемый" - это концепция времени компиляции, а на самом деле это касается значений времени выполнения и, следовательно, должно производить undefinedповедение. Кроме того, существуют и другие контексты, в которых lvalues может происходят, например, левый операнд. или. *, которые также должны быть ограничено. Требуется дополнительная подготовка.
... , что не влияет на логику. Опять же, следует отметить, что эта проблема даже предшествует С++ 03, что делает ее менее убедительной, когда мы приближаемся к С++ 17.
CWG-issue # 315, похоже, также охватывает ваш случай:
Еще один пример для рассмотрения - вызов функции-члена от нулевого указателя:
struct A { void f () { } }; int main () { A* ap = 0; ap->f (); }
[...]
Обоснование (октябрь 2003 г.):
Мы согласились, что пример должен быть разрешен.
p->f()
переписывается как(*p).f()
в соответствии с 5.2.5 [expr.ref].*p
не является ошибкой, когдаp
имеет значение null, если значение lvalue не преобразуется в значение r (4.1 [conv.lval]), которого нет здесь.
В соответствии с этим обоснованием косвенность через нулевой указатель сама по себе не вызывает UB без дальнейших преобразований lvalue-to-rvalue (= обращается к сохраненному значению), привязки ссылок, вычисления значений или тому подобное. (Nota bene: вызов нестатической функции-члена с нулевым указателем должен вызывать UB, хотя и просто туманно запрещен [class.mfct.non-static]/2. Обоснование устарело в этом отношении.)
т.е. простой оценки *d
недостаточно для вызова UB. Идентификация объекта не требуется, и ни одно из них не является ранее сохраненным значением. С другой стороны, например,
*p = 123;
есть undefined, так как вычисляется значение левого операнда, [expr.ass]/1:
Во всех случаях назначение упорядочивается после вычисления значения правого и левого операндов
Поскольку левый операнд, как ожидается, будет значением glvalue, идентификация объекта, на который ссылается этот glvalue, должна определяться, как указано в определении оценки выражения в [intro.execution]/12, что невозможно ( и, таким образом, приводит к UB).
1 [expr]/11:
В некоторых контекстах выражение появляется только для его побочных эффектов. Такое выражение называется выражением отбрасываемого значения. The выражение оценивается и его значение отбрасывается. [...]. Преобразование lvalue-to-rvalue (4.1) является применяется тогда и только тогда, когда выражение является значением gl нестабильный тип и [...]