В каких версиях стандарта C++ поведение "(i+=10)+=10" не определено?

В C++ имеет ли неопределенное поведение следующее:

int i = 0;
(i+=10)+=10;

В комментариях к моему ответу на мой ответ на вопрос " Каков результат + = в C и C++" возникли некоторые споры ? Тонкость здесь заключается в том, что ответ по умолчанию кажется "да", тогда как кажется, что правильный ответ "это зависит от версии стандарта C++".

Если это зависит от версии стандарта, пожалуйста, объясните, где это UB, а где нет.

3 ответа

Решение

tl; dr: последовательность изменений и чтений, выполненных в (i+=10)+=10 хорошо определено как в C++98, так и в C++11, однако в C++ 98 этого недостаточно для определения поведения.

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

C++11 не имеет точек последовательности и требует только того, чтобы модификации объекта были упорядочены относительно друг друга и считывания одного и того же объекта для получения определенного поведения.

Поэтому поведение не определено в C++98, но хорошо определено в C++11.


С ++ 98

C++ 98 пункт [expr] 5 p4

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

C++ 98 пункт [expr.ass] 5.17 p1

Результатом операции присваивания является значение, сохраненное в левом операнде после присвоения; результат lvalue

Поэтому я считаю, что порядок указан, однако я не вижу, что одного этого достаточно, чтобы создать точку последовательности в середине выражения. И продолжаем с цитатой [expr] 5 p4:

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

Поэтому, даже если порядок указан, мне кажется, что этого недостаточно для определенного поведения в C++ 98.


C++11

C++11 убирает точки последовательности для более ясного представления о последовательности до и после после. Язык из C++ 98 заменен на

C++11 [intro.execution] 1,9 стр. 15

За исключением отмеченных случаев, оценки операндов отдельных операторов и подвыражений отдельных выражений не являются последовательными. [...]

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

C++11 [expr.ass] 5.17 p1

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

Таким образом, хотя упорядочения было недостаточно для того, чтобы поведение, определенное в C++98, в C++11 изменилось требование, так что упорядоченность (т. Е. Последовательность) достаточна.

(И мне кажется, что дополнительная гибкость, обеспечиваемая "последовательность до" и "последовательность после", привела к гораздо более четкому, последовательному и четко определенному языку.)


Мне кажется маловероятным, что любая реализация C++ 98 действительно сделает что-то удивительное, когда последовательность операций четко задана, даже если этого недостаточно для создания технически хорошо определенного поведения. Например, внутреннее представление этого выражения, созданного Clang в режиме C++98, имеет четко определенное поведение и выполняет ожидаемые действия.

В C++11 выражение хорошо определено и приведет к i == 20,

От [expr.ass]/1:

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

Это означает, что назначение i+=1 последовательность перед вычислением значения левой части (i+=10)+=10который, в свою очередь, упорядочен перед окончательным назначением i,


В C++03 выражение имеет неопределенное поведение, потому что оно вызывает i быть измененным дважды без промежуточной точки последовательности.

Может быть. Это зависит от версии C++.

В C++03 это очевидный UB, между назначениями нет промежуточной точки последовательности.

В C++11, как объясняет Mankarse, он больше не является неопределенным - составное назначение в скобках упорядочено перед внешним, так что все в порядке.

Другие вопросы по тегам