2013-07-01 4 views
18

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

int i = 0; 

i = i++; // #0 

i = ++i; // #1 

На SO есть две темы, которые объясняют эти примеры в контексте C++ 11. Here было сказано, что #0 вызывает UB и #1 четко определен. Here было сказано, что оба примера не определены. Эта двусмысленность меня смущает. Я читал это хорошо структурированное reference три раза, но тема кажется слишком сложной для меня.

.

Давайте проанализируем пример : i = i++;.

Соответствующие цитаты:

  • Значение вычисления встроенного postincrement и postdecrement операторов секвенировали перед его побочным эффектом.

  • Побочный эффект (модификация левого аргумента) из встроенных в оператора присваивания и всех встроенных операторов присваивания секвенируют после вычисления значения (но не побочные эффекты) из и левой и правые аргументы, и секвенировали до вычисления выражения присваивания значения (то есть, перед возвращением ссылки на модифицированном объект)

  • Если побочный эффект от скалярного объекта unsequenced по отношению к другой стороне эффект на один и тот же скалярный объект, поведение не определено.

Как я понимаю, побочный эффект оператора присваивания не секвенировали с побочными эффектами от его левой и правой аргументы. Таким образом, побочный эффект оператора присваивания не секвенирован с побочными эффектами i++. Итак, #0 вызывает UB.

.

Давайте проанализируем пример #1: i = ++i;.

Соответствующие цитаты:

  • Побочный эффект встроенных прединкремента и predecrement операторов секвенируют до его вычисления значений (неявное правило из-за к определению, как присвоение соединения)

  • Побочный эффект (модификация левого аргумента) встроенного оператора присваивания и всех встроенных операторов присваивания соединений секвенировали после вычисления значения (но не побочные эффекты) из и левых и правых аргументов, и секвенировали до вычисления значения выражения присваивания (то есть, прежде чем вернуться ссылка на модифицированном объекта)

  • Если побочный эффект скалярного объекта не зависит от другого побочного эффекта на том же скалярном объекте, поведение не определено.

Я не могу видеть, как этот пример отличается от #0. Кажется, это UB для меня по той же причине, что и #0. Побочный эффект назначения не секвенирован с побочным эффектом ++i. Кажется, это UB. Приведенная выше тема говорит, что она четко определена. Зачем?

.

Вопрос: как я могу применить процитированные правила для определения UB из примеров. Было бы очень полезно получить как можно более простое объяснение. Спасибо!

+5

У меня был бы довольно откровенный разговор с программистом, если бы я увидел это в производственном коде. Да, это интеллектуальное curiorisity, но не может видеть много заслуг в том, чтобы думать об этом слишком тяжело. Это, сказал, +1 для хорошо написанного вопроса, и я подробно прочитаю принятый ответ. – Bathsheba

+2

@ Bathsheba Я согласен, что эти примеры кода, вероятно, никогда не будут в реальном коде, но я думаю, что понимание стандарта поможет определить UB в некоторых других примерах. – Kolyunya

+0

Я поддержал вас по качеству вопроса и, как и вы, дождался ответа подобного качества. – Bathsheba

ответ

9

Поскольку ваши кавычки не относятся непосредственно к стандарту, я попытаюсь дать подробный ответ со ссылкой на соответствующие части стандарта. Определения «побочные эффекты» и «оценка» находятся в пункте 1.9/12:

Доступ объекта, назначенный летучим glvalue (3.10), изменение объекта, вызов библиотеки ввод/функция вывода или вызов функции, которая выполняет любую из этих операций, - это все побочные эффекты, которые являются изменениями состояния среды выполнения. Оценка выражения (или подвыражения) в общем случае включает в себя как вычисления значений (включая определение идентичности объекта для оценки значения glvalue и выборку значения, ранее назначенного объекту для оценки prvalue), так и инициирование побочных эффектов.

Следующая соответствующая часть пункта 1,9/15:

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

Теперь давайте посмотрим, как применить это к двум примерам.

i = i++; 

Это постфиксная форма приращения, и вы найдете ее определение в пункте 5.2.6. Наиболее важное предложение гласит:

Значение Вычисление ++ выражений секвенировало до изменения объекта операнда.

Для выражения присваивания см. Пункт 5.17. В соответствующей части указано:

Во всех случаях назначение выполняется после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания.

Используя всю информацию сверху, оценка всего выражения (этот порядок не гарантируется стандартом!):

  • значение вычисление i++ (правая сторона)
  • значение вычисления i (левой стороны)
  • модификации i (побочный эффект ++)
  • модификация i (побочный эффект от =)

Все стандартные гарантии заключаются в том, что вычисления значений двух операнды секвенированы перед вычислением значения выражения присваивания. Но вычисление стоимости правой стороны является только «считыванием значения i» и не, изменяя i, две модификации (побочные эффекты) не упорядочены относительно друг друга, и мы получаем неопределенное поведение.

Как насчет второго примера?

i = ++i; 

Здесь ситуация совсем иная. Вы найдете определение приращения префикса в пункте 5.3.2. Соответствующая часть:

Если x не имеет тип bool, выражение ++ x эквивалентно x + = 1.

Подставив, что наше выражение эквивалентно

i = (i += 1) 

Подняв оператор присваивания соединение += в 5,17/7, получаем, что i += 1 эквивалентно i = i + 1 за исключением того, что i только вычисляется один раз. Следовательно, выражение в вопросе, наконец, становится

я = (я = (я + 1))

Но мы уже знаем из выше, что вычисление значения = секвенировало после вычисления значения операндов и побочных эффектов секвенированы перед вычислением значения =. Таким образом, мы получаем четко определенный порядок оценки:

  1. вычислений значения i + 1i - с левой стороны внутреннего выражения) (# 1)
  2. инициировать побочный эффект внутреннего =, то есть изменить «внутренним "i
  3. значение вычисляют из (i = i + 1), что„новый“значение i
  4. инициировать побочный эффект внешнего =, то есть изменить„внешний“i
  5. вычислить значение полного выражения.

(# 1): Здесь, i только вычисляется один раз, так как i += 1 эквивалентно i = i + 1 исключением того, что i только один раз (оцениваемой 5,17/7).

+0

Благодарим вас за подробный ответ. Если я правильно понял, то описываемый порядок оценки второго примера: вычислять аргументы внутреннего присваивания ==> побочные эффекты внутреннего присваивания ==> вычислить значение inner assignmnet ==> побочные эффекты внешнего присваивания ==> вычислить значение внешнее назначение. – Kolyunya

+0

@ Kolyunya Я обновил свой ответ. Надеюсь, это ответит на ваш вопрос. – MWid

+0

Спасибо. Кажется, я все понял. – Kolyunya

8

Основное различие заключается в том, что ++i определяется как i += 1, так что

i = ++i; 

такая же, как:

i = (i += 1); 

Поскольку побочные эффекты оператора += секвенируют, прежде чем вычисление значения оператора, фактическая модификация от i в ++i секвенирована перед внешним назначением. Этот следует непосредственно из разделов, которые вы цитируете: «Побочный эффект (модификация левого аргумента) встроенного назначения оператор и все встроенные операторы присваивания соединений: после вычисления значения (но не побочные эффекты) оба левых и правых аргументов, и секвенировали, прежде чем вычисления на значения выражения присваивания (то есть, до возвращают ссылку на модифицированном объект)»

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

И как вы косвенно указываете, это ново для C++ 11; ранее, оба не были определены. В более старых версиях C++ использовались точки последовательности, а не секвенированные ранее, и там не было точкой последовательности ни в одном из операторов присваивания. (I создают впечатление, что у операторов, которые приводят к lvalue, есть значение, которое секвенируется после любых побочных эффектов . В более раннем C++ выражение *&++i было неопределенным поведением, в C++ 11 оно Гарантируется, что они будут такими же, как .)

+1

Спасибо за ответ Джеймса. Не могли бы вы объяснить, почему это правда, возможно, с некоторыми ссылками? 'Поскольку побочные эффекты оператора + = секвенированы перед вычислением значения оператора, фактическая модификация i в ++ i секвенируется перед внешним присваиванием. ' – Kolyunya

+1

В цитате в моем вопросе говорится, что побочный эффект первое присваивание в 'i = (i + = 1);' не секвенируется побочным эффектом его правого аргумента - '(i + = 1)'. Не понимаю ли я это? – Kolyunya

+1

Я не знаю, только ли я не настолько умен, чтобы понять это, но, возможно, очень подробное объяснение поможет мне и другим людям. – Kolyunya

Смежные вопросы