Порядок оценки и поведение undefined
Говоря в контексте стандарта С++ 11 (который уже не имеет понятия точек последовательности, как вы знаете), я хочу понять, как определяются два простых примера.
int i = 0;
i = i++; // #0
i = ++i; // #1
На SO есть две темы, которые объясняют эти примеры в контексте С++ 11. Здесь сказано, что #0
вызывает UB и #1
. Здесь сказано, что оба примера: undefined. Эта двусмысленность меня смущает. Я читал эту хорошо структурированную reference три раза, но тема кажется слишком сложной для меня.
.
Проанализируем пример #0
: i = i++;
.
Соответствующие кавычки:
-
Вычисление стоимости встроенного постинкремента и постдекремента операторы секвенированы перед его побочным эффектом.
-
Побочный эффект (модификация левого аргумента) встроенного оператора присваивания и всех встроенных операторов присваивания секвенируется после вычисления значения (но не побочных эффектов) как левый, так и правый аргументы, и секвенирован до значения вычисление выражения присваивания (то есть до возвращения ссылка на измененный объект)
-
Если побочный эффект скалярного объекта не зависит от другого побочный эффект на том же скалярном объекте, поведение undefined.
Как я понял, побочный эффект оператора присваивания не секвенирован с побочными эффектами его левого и правого аргументов. Таким образом, побочный эффект оператора присваивания не секвенирован с побочными эффектами i++
. Итак, #0
вызывает UB.
.
Проанализируем пример #1
: i = ++i;
.
Соответствующие кавычки:
-
Побочный эффект встроенного преинкремента и предопределения операторы упорядочиваются до вычисления его значений (неявное правило, подлежащее для определения как составного назначения)
-
Побочный эффект (модификация левого аргумента) встроенного оператора присваивания и всех встроенных операторов присваивания секвенируется после вычисления значения (но не побочных эффектов) как левый, так и правый аргументы, и секвенирован до значения вычисление выражения присваивания (то есть до возвращения ссылка на измененный объект)
-
Если побочный эффект скалярного объекта не зависит от другого побочный эффект на том же скалярном объекте, поведение undefined.
Я не вижу, как этот пример отличается от #0
. Кажется, это UB для меня по той же причине, что и #0
. Побочный эффект присваивания не связан с побочным эффектом ++i
. Кажется, это UB. Приведенная выше тема говорит, что она четко определена. Почему?
.
Вопрос: как я могу применить котируемые правила для определения UB примеров. Было бы очень полезно получить как можно более простое объяснение. Спасибо!
Ответы
Ответ 1
Поскольку ваши кавычки не относятся непосредственно к стандарту, я попытаюсь дать подробный ответ, цитируя соответствующие части стандарта. Определения "побочные эффекты" и "оценка" приведены в параграфе 1.9/12:
Доступ к объекту, обозначенному изменчивым значением glvalue (3.10), модификацией объекта, вызовом функции ввода-вывода библиотеки или вызовом функции, выполняющей любые из этих операций, являются побочными эффектами, которые являются изменениями состояния среда выполнения. Оценка выражения (или подвыражения) в общем случае включает в себя как вычисления значений (включая определение идентичности объекта для оценки glvalue и получение значения, ранее назначенного объекту для оценки prvalue), так и инициирование побочных эффектов.
Следующая соответствующая часть - это пункт 1.9/15:
За исключением тех случаев, когда отмечено, оценки операндов отдельных операторов и подвыражений отдельных выражений не имеют никакого значения. [...] Вычисления значений операндов оператора секвенируются перед вычислением значения результата оператора. Если побочный эффект на скалярном объекте не влияет на какой-либо другой побочный эффект на один и тот же скалярный объект или вычисление значения с использованием значения одного и того же скалярного объекта, поведение undefined.
Теперь посмотрим, как применить это к двум примерам.
i = i++;
Это постфиксная форма приращения, и вы найдете ее определение в пункте 5.2.6. Наиболее релевантное предложение гласит:
Вычисление значения выражения ++ секвенируется до модификации объекта операнда.
Для выражения присваивания см. параграф 5.17. В соответствующей части указано:
Во всех случаях назначение выполняется после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания.
Используя всю информацию сверху, оценка всего выражения (этот порядок не гарантируется стандартом!):
- вычисление значения
i++
(правая сторона)
- вычисление значения
i
(левая сторона)
- модификация
i
(побочный эффект ++
)
- модификация
i
(побочный эффект =
)
Все стандартные гарантии заключаются в том, что вычисления значений двух операндов секвенированы перед вычислением значения выражения присваивания. Но вычисление стоимости правой стороны - это только "чтение значения i
" и не изменение i
, две модификации (побочные эффекты) не секвенированы относительно друг друга и мы получаем поведение undefined.
Как насчет второго примера?
i = ++i;
Ситуация здесь совсем другая. Вы найдете определение приращения префикса в пункте 5.3.2. Соответствующая часть:
Если x не относится к типу bool, выражение ++ x эквивалентно x + = 1.
Подставляя это, наше выражение эквивалентно
i = (i += 1)
Взглянув на составной оператор присваивания +=
в 5.17/7, получим, что i += 1
эквивалентно i = i + 1
, за исключением того, что i
оценивается только один раз. Следовательно, рассматриваемое выражение, наконец, становится
i = (i = (i + 1))
Но мы уже знаем выше, что вычисление значения =
секвенируется после вычисления значения операндов, а побочные эффекты секвенированы перед вычислением значения =
. Итак, мы получаем четко определенный порядок оценки:
- значение вычисления
i + 1
(и i
- левая сторона внутреннего выражения) (# 1)
- инициировать побочный эффект внутреннего
=
, т.е. изменить "внутренний" i
- значение вычисления
(i = i + 1)
, которое является "новым" значением i
- инициировать побочный эффект внешнего
=
, т.е. изменить "внешний" i
- вычислить значение полного выражения.
(# 1): Здесь i
оценивается только один раз, поскольку i += 1
эквивалентен i = i + 1
, за исключением того, что i
оценивается только один раз (5.17/7).
Ответ 2
Основное различие заключается в том, что ++i
определяется как i += 1
, поэтому
i = ++i;
совпадает с:
i = (i += 1);
Так как побочные эффекты оператора +=
секвенированы до
вычисление значения оператора, фактическая модификация
из i
в ++i
секвенируется перед внешним назначением. Эта
следует непосредственно из разделов, которые вы цитируете: "Побочный эффект
(модификация левого аргумента) встроенного назначения
оператора и всех встроенных операторов присваивания соединений
после вычисления значения (но не побочных эффектов)
как левого, так и правого аргументов, и секвенирован до
вычисление значения выражения присваивания (то есть до
возвращая ссылку на модифицированный объект) "
Это связано с вложенным оператором присваивания; внешний)
оператор присваивания только накладывает последовательность на значение
вычисление его операндов, а не их побочных эффектов. (Но
Конечно, это не отменяет последовательность, введенную иначе.)
И как вы косвенно указываете, это ново для С++ 11;
ранее оба были undefined. Более старые версии С++
использовались точки последовательности, а не секвенированы раньше, и там
не была точкой последовательности в любом из операторов присваивания. (Я
создается впечатление, что целью было то, что операторы, которые
результат в lvalue имеет значение, которое секвенируется после любого
побочные эффекты. В более раннем С++ выражение *&++i
было
undefined поведение; в С++ 11 гарантируется, что он будет таким же, как
++i
.)