Изначально я привел более сложный пример, этот был предложен @n. 'местоимения' м. в теперь удаленном ответе. Но вопрос стал слишком длинным, смотрите историю редактирования, если вам интересно.
Имеет ли следующая программа четко определенное поведение на C ++ 17?
int main()
{
int a=5;
(a += 1) += a;
return a;
}
Я считаю, что это выражение четко определено и оценивается следующим образом:
- Правая сторона
a
оценивается как 5. - Побочных эффектов правой стороны нет.
- Левая часть оценивается как ссылка на
a
,a += 1
точно определена. - Выполняется левосторонний побочный эффект, в результате чего получается
a==6
. - Присваивание вычисляется, добавляя 5 к текущему значению
a
, что делает его 11.
Соответствующие разделы стандарта:
Выражение X считается упорядоченным перед выражением Y, если каждое вычисление значения и каждый побочный эффект, связанный с выражением X, упорядочен перед каждым вычислением значения и каждым побочным эффектом, связанным с выражением Y.
[expr.ass] / 1 (выделено мной):
Оператор присваивания (=) и составные операторы присваивания группируются справа налево. Все требуют изменяемого lvalue в качестве левого операнда; их результат - lvalue, относящийся к левому операнду. Результатом во всех случаях является битовое поле, если левый операнд является битовым полем. Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания. Правый операнд упорядочивается перед левым операндом. Что касается вызова функции с неопределенной последовательностью, операция составного присваивания является однократной оценкой.
Формулировка изначально взята из принятой статьи P0145R3 < / а>.
Я чувствую, что во втором разделе есть некоторая двусмысленность и даже противоречие.
Правый операнд ставится перед левым операндом.
Вместе с определением последовательность до настоятельно подразумевает порядок побочных эффектов, но предыдущее предложение:
Во всех случаях присваивание выполняется после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания.
только явно упорядочивает присвоение после вычисления значения, но не их побочные эффекты. Таким образом, разрешая такое поведение:
- Правая часть
a
оценивается как 5. - Левая сторона оценивается как ссылка
a
,a += 1
точно определена. - Присваивание вычисляется, добавляя 5 к текущему значению
a
, что делает его 10. - Выполняется левый побочный эффект, делая
a==11
или даже6
, если старые значения использовались даже для побочного эффекта.
Но этот порядок явно нарушает определение упорядочено до, поскольку побочные эффекты левого операнда произошли после вычисления значения правого операнда. Таким образом, левый операнд не был упорядочен после правого операнда, что нарушает указанное выше предложение. Нет, я пошутил. Это допустимое поведение, правда? Т.е. присвоение может чередовать оценку справа-слева. Или это можно сделать после обеих полных оценок.
Выполнение кода gcc выводит 12, clang 11. Кроме того, gcc предупреждает о
<source>: In function 'int main()':
<source>:4:8: warning: operation on 'a' may be undefined [-Wsequence-point]
4 | (a += 1) += a;
| ~~~^~~~~
Я ужасно читаю сборку, может кто-нибудь сможет хоть как то переписать gcc до 12? (a += 1), a+=a
работает, но это кажется неправильным.
Что ж, если подумать, правая сторона также оценивает ссылку на a
, а не только на значение 5. Так что Gcc все еще может быть прав, в этом случае clang может быть неправильным.
*=
и+=
в одной строке? Просто разбейте его на две строчки. Даже если он был упорядочен правильно, он все равно будет нечитаемым для людей (или увеличит когнитивную нагрузку, если вы хотите более мягкое утверждение) - person Jeffrey   schedule 04.10.20202
иArgs
, не имеет значения. Другим важным правилом является правило № 8: побочный эффект (модификация левого аргумента) встроенного оператора присваивания и всех встроенных операторов составного присваивания упорядочивается после вычисления значения (но не побочных эффектов) обоих. левый и правый аргументы, и упорядочивается перед вычислением значения выражения присваивания (то есть перед возвратом ссылки на измененный объект). - person aschepler   schedule 04.10.2020res*=2
возвращает ссылку, поскольку вычисление значения должно произойти доref+=...
, но остается неупорядоченным с учетом, когдаres*=2
действительно выполняется. Таким образом, приращение может произойти до умножения? Кажется, противоречит № 20. Тем не менее, вы можете дать ответ? - person Quimby   schedule 04.10.2020|=
. Я не написал ответа, потому что до сих пор не уверен, в чем здесь проблема. - person aschepler   schedule 04.10.2020(a += 1) += a;
просто не определено, но я не понимаю, как это сделать. - person Quimby   schedule 04.10.2020(a += 1) += a;
, удалите все остальное. В противном случае задайте новый вопрос об этом и укажите ссылку на этот вопрос в качестве мотивации, если хотите. (Обратите внимание, что вопрос уже требовал 2 вещей, объяснения и исправления. Это может быть хорошим способом разделить это). - person cigien   schedule 04.10.2020a
- я думаю, что преобразование lvalue-to-rvalue требуется для [basic.lval] / 6 для операнда следует рассматривать как вычисление значения этого операнда. Возможно, и определение идентификатора glvalue, и доступ для чтения преобразования lvalue-to-rvalue - это два вычисления значений одного и того же выражения? - person aschepler   schedule 04.10.2020static_assert
, по-видимому, выполняется в другом порядке, и результаты отличаются от оценки за пределамиstatic_assert
. Это приемлемо, если поведение не определено, однако я считаю, что если clang считает операции неупорядоченными, он должен предупредить об этом, как это происходит с-std=c++14
(где они не упорядочены). - person n. 1.8e9-where's-my-share m.   schedule 04.10.2020static_assert()
. - person Deduplicator   schedule 04.10.2020