Двойное присвоение одной и той же переменной в одном выражении в C ++ 11

Стандарт C ++ 11 (5.17, expr.ass ) говорится, что

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

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

Будет ли данный код:

int a = 0;
a = (a+=1) = 10;

if ( a == 10 ) {
    printf("this is defined");
} else {
    printf("undefined"); 
}

всегда оценивать до a==10?


person Dariusz    schedule 25.10.2013    source источник
comment
Боковое примечание: проверка результата возможно неопределенного выражения не говорит нам, действительно ли это UB или нет. Это может быть UB и дать правильный результат.   -  person jrok    schedule 25.10.2013
comment
@jrok, это пример кода, возможно, даже SSCCE, поскольку SO требует, чтобы я включил действительный код. Я действительно пробовал протестировать его, просто из любопытства, но понимаю, что это ничего не доказывает; поэтому я даже не упомянул об этом.   -  person Dariusz    schedule 25.10.2013
comment
@jrok - может дать правильный результат. Цитаты важны. <g>   -  person Pete Becker    schedule 25.10.2013


Ответы (3)


Давайте перепишем ваш код как

E1 = (E2 = E3)

где E1 - это выражение a, E2 - это выражение a += 1, а E3 - выражение 10. Здесь мы использовали, что оператор присваивания группируется справа налево (§5.17 / 1 в стандарте C ++ 11).

В §5.17 / 1 также говорится:

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

Применение этого к нашему выражению означает, что мы сначала должны оценить подвыражения E1 и E2 = E3. Обратите внимание, что между этими двумя оценками нет связи «упорядочено до», но это не вызывает проблем.

Оценка id-выражения E1 тривиальна (результатом является сам a). Оценка выражения-присваивания E2 = E3 происходит следующим образом:

Сначала нужно вычислить оба подвыражения. Оценка литерала E3 снова тривиальна (дает значение prvalue 10).

Оценка (составного) выражения-присваивания E2 выполняется в следующие шаги:

1) Поведение a += 1 эквивалентно a = a + 1, но a оценивается только один раз (§5.17 / 7). После оценки подвыражений a и 1 (в произвольном порядке) к a применяется преобразование lvalue-to-rvalue для чтения значения, хранящегося в a.

2) Значения a (то есть 0) и 1 добавляются (a + 1), и результатом этого сложения является prvalue значения 1.

3) Прежде чем мы сможем вычислить результат присваивания a = a + 1, значение объекта, на который ссылается левый операнд, заменяется значением правого операнда (§5.17 / 2). Результатом E2 будет lvalue, относящееся к новому значению 1. Обратите внимание, что побочный эффект (обновление значения левого операнда) выполняется до вычисления значения выражения присваивания. Это §5.17 / 1, процитированный выше.

Теперь, когда мы вычислили подвыражения E2 и E3, значение выражения, на которое ссылается E2, заменяется значением E3, которое равно 10. Следовательно, результатом E2 = E3 является lvalue значения 10.

Наконец, значение выражения, на которое ссылается E1, заменяется значением выражения E2 = E3, которое мы вычислили как 10. Таким образом, переменная a заканчивается и содержит значение 10.

Поскольку все эти шаги четко определены, все выражение дает четко определенное значение.

person MWid    schedule 30.10.2013

Да, было изменение между C ++ 98 и C ++ 11. Я считаю, что ваш пример четко определен в правилах C ++ 11, но демонстрирует неопределенное поведение в соответствии с правилами C ++ 98.

В качестве более простого примера x = ++x; не определено в C ++ 98, но четко определено в C ++ 11. Обратите внимание, что x = x++; все еще не определено (побочный эффект постинкремента не имеет последовательности с вычислением выражения, в то время как побочный эффект предварительного инкремента упорядочивается перед тем же).

person Igor Tandetnik    schedule 25.10.2013
comment
Это интересно и полезно знать, хотя вы, к сожалению, не обращаетесь к опубликованному коду. Есть ли у вас какие-либо мысли о том, что это неопределенное / неопределенное поведение? - person Dariusz; 25.10.2013
comment
Я считаю, что ваш пример четко сформулирован. Обновил ответ соответственно. - person Igor Tandetnik; 25.10.2013

Проведя небольшое исследование, я убедился, что поведение вашего кода хорошо определено в C ++ 11.

$ 1,9 / 15 утверждает:

Вычисления значений операндов оператора последовательно выполняются перед вычислением значения результата оператора.

В $ 5.17 / 1 говорится:

Оператор присваивания (=) и составные операторы присваивания группируются справа налево.

Если я правильно понял, в вашем примере

a = (a+=1) = 10;

это означает, что вычисления значений (a+=1) и 10 должны быть выполнены до вычисления значения (a+=1) = 10, и вычисление значения этого выражения должно быть завершено до того, как будет оценено a = (a+=1) = 10;.

В $ 5.17 / 1 говорится:

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

Это означает, что присвоение должно произойти до вычисления значения, и, следовательно, из-за транзитивности оценка (a+=1) = 10 может начаться только после присвоения a+=1 (поскольку его значение может быть вычислено только после побочного эффекта).

То же верно и для второго и третьего задания.

См. Также этот отличный ответ, в котором гораздо более подробно и лучше, чем я мог бы, объясняется отношение «упорядочено до».

person Philipp Lenk    schedule 29.10.2013