Порядок оценки и поведение 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 + 1i - левая сторона внутреннего выражения) (# 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.)