и CLang не сложились мнения (т.е. как минимум не строго определенное поведение; GCC предупреждает о "possibly unsequenced evaluation" и выдает результат на единицу больше, чем не предупреждающий о подобном CLang; сборка с -std=c++20 -O0 -Wall -Wextra -Werror):
int a{};
++a += a;
В соответствии с [intro.execution], искомое выражение (++a += a) является full-expression и подразбиваемо на подвыражения (условная нотация):
[expr.ass+] (
[expr.pre.incr+] ( a ),
constituent-expression ( a )
)
1. [expr.pre.incr+] модифицирует операнд.
2. [expr.ass+] модифицирует операнд (1, 2).
3. Модификация объекта является побочным эффектом, вычисление подвыражений инициирует их (побочных эффектов) применение.
4. Отношения sequenced-before/sequenced-after между подвыражениями предполагают определенность в их пределах порядка между применением побочных эффектов и вычислением значений.
5. По-умолчанию такой порядок не определен, вытекающие из чего неоднозначности, если возникают, приводят к неопределенному поведению.
В соответствии с описанием упорядоченности вычислений с оператором [expr.ass], вычисление обоих операндов sequenced-before непосредственно присваивания. Кроме того, вычисление правого операнда sequenced-before вычисления левого.
Таким образом представляется, что все надлежащие гарантии упорядоченности присутствуют, а выражение (++a += a) должно выполняться как если бы было переписано нижеследующим образом:
/* constituent-expression ( a ) since rhs is sequenced-before lhs */
int rhs{a};
/* [expr.pre.incr+] ( a ) since lhs is sequenced-after rhs */
++a;
/* [expr.ass+] since assignment is sequenced-after both lhs and rhs */
a += rhs;
Ожидаемым результатом является 1, что соответствует выполнению артефакта от CLang.
Вопрос: где в вышеприведенной логике ошибка, допускающая несогласованность порядка вычисления (учитывая наблюдаемые результаты)? Подобное поведение можно было бы объяснить буквальной (уровня макроподстановок) трактовкой этого положения (поскольку + действительно unsequenced), однако, с другой стороны, для (compound-)assignment предписаны явные гарантии обратного.
Не делай так
Вот я и хочу узнать, как именно.
Что не делать или что делать? - Разбить выражение на явное указание намерений. - Не писать таких выраженний
Мне бы с т.з. стандарта проблему найти.
Так интересно же почему так реализовано в компиляторах.
реальность не всегда совпадает со стандартом. :)
Вот вам еще квест))) ++a *= a;
Если для встроенных типов - оно же, с разницей в это и следующее предложения (нерелевантно в рассматриваемом случае).
Да пофиг на стандарт gcc и clang не дают никаких варнингов и дают разные результаты)
Результаты аналогичны (включая предупреждения при построении с -std=c++20 -O0 -Wall -Wextra -Werror).
как не дают если дают
кажется, gcc игнорирует, что правая часть должна вычисляться до левой, и делает как-то так: int rhs{++a + a}; a = rhs, а порядок вычисления слагаемых не определен
Сppinsights тоже, выходит, некорректно разбирает...
когда в стандарте были точки следования, приведённый код являлся UB, сейчас также, только вместо точек следования какой-то новый способ описания всего этого, охватывающий многопоточку
Мы кругами ходим. Если утверждаете, что неопределенность, - пожалуйста, подтверждайте ссылками.
этот код содержал неопределенное поведение и после того, как sequence point заменили на sequenced-before. 11 и 14 стандарты
увы ссылок я уже не найду, но помню что до 11ого стандарты были правила с точками следования, и суть была такая, что между точками следования нельзя менять переменные более одного раза
я этого не отрицал, просто правила с точками следования мне показались намного проще
Я про сейчас (хотя бы с семнадцатого).
там суть не меняли, просто описали тоже поведение другим способом
если бы точки следования дожили до p0145, то в этом выражении просто добавилось бы sequence point внутри так же, как добавилось sequenced-before отношений
все было бы то же самое, что мы имеем сегодня, просто по-другому сформулировано
тут, наверное, прикол не в порядке вычислений
-Wsequence-point на GCC ругается, опять же)
в доке gcc сказано что-то вроде "оно будет ругаться для совместимости с C"
Поведение тоже некорректное оказывается.
заметил, что в [expr.ass] используется слово "computation", но не "evaluation", есть какие-либо комментарии по этому поводу
просто в 3ем пункте именно evaluation инициирует побочные эффекты
Вот это интересное наблюдение, конечно. Здесь evaluation (под)выражения (причем с пометкой "in general"!) определяется как value computation (identity determination для glvalue и value fetch для prvalue) и инициация сайд-эффектов. Следом определяется отношение sequenced-before между evaluations (и только ими). Чуть далее отношение определяется для expressions (где value computations и side-effect applications снова разделяются). Т.е. до этого момента отношения упорядоченности выполнения относились исключительно к (и определялись для) вычислению выражений. Наконец, присваивание считается sequenced-after именно value computations для левого и правого операндов, однако сами операнды (подвыражения) все еще называются sequenced-before друг для друга (целиком). Таким образом тоже оказывается любопытно, огрех ли это wording'а для [expr.ass] (и подразумевалась синонимичность), либо же подразумевалось буквально написанное, но не определенное ранее в [intro.execution]...
а может сайд эффект быть произведён до вычисления выражения скажем в ++a?
Инициировано применение должно будет быть при его вычислении, да, однако только sequenced-before/after может гарантировать окончание наложения, а value computation или сайд-эффект (даже инициация, предположительно) для той же memory location без таких гарантий приводит к возникновению неопределенного поведения. Таким образом, если понимать буквально вышеупомянутый вординг [expr.ass], применение сайд-эффектов от левого подвыражения-операнда оказывается unsequenced относительно сайд-эффекта самого присваивания, а т.к. объект (и, транзитивно, memory location) один - UdB. Проблема, однако, в том, что я при изначальном разборе тестировал такой вариант (++a += 1), и он не вызывает ни срабатывания предупреждения, ни различного наблюдаемого поведения на разных компиляторах. Edit: Т.е. возможно это все еще баг поверх стороннего wording issue.
До 11 стандарта это (я о примере с += 1) однозначно UB, ибо у меня в мозгу железно отложилось правило не меняй переменную более раза между точками следования
Рассматриваем в условиях хотя бы семнадцатого.
Обсуждают сегодня