При просмотре кода какого-либо проекта я столкнулся с следующим утверждением:
++x %= 10;
Является ли этот оператор хорошо определенным в С++ или он попадает в ту же категорию, что и
a[i] = i++
?
При просмотре кода какого-либо проекта я столкнулся с следующим утверждением:
++x %= 10;
Является ли этот оператор хорошо определенным в С++ или он попадает в ту же категорию, что и
a[i] = i++
?
В соответствии с С++ 11 1.9 Program execution /15
:
За исключением тех случаев, когда отмечено, оценки операндов отдельных операторов и подвыражений отдельных выражений не подвержены.
Если побочный эффект скалярного объекта не зависит от другого побочного эффекта для одного и того же скалярного объекта или вычисления значения с использованием значения одного и того же скалярного объекта, поведение undefined.
В этом случае я считаю, что ++x
является побочным эффектом, а x %= 10
- вычислением значений, поэтому вы думаете, что это будет поведение undefined. Тем не менее, раздел назначения (5.17 /1
) имеет это, чтобы сказать (мой жирный):
Во всех случаях присваивание секвенируется после вычисления значения правого и левого операндов и перед вычислением значения выражения присваивания.
Следовательно, это означает, что обе стороны секвенированы перед назначением и до того, как результат присвоения станет доступным. И поскольку стандарт также утверждает (5.17 /7
), что x OP = y
идентичен x = x OP y
, но с x
оценивается только один раз, получается, что это хорошо определенное поведение, так как оно эквивалентно:
++x = Q % 10; // where Q is the value from ++x, not evaluated again.
Остается только вопрос, какая сторона присваивания оценивается, поскольку они не упорядочены. Тем не менее, я не думаю, что это важно в этом случае, поскольку оба они будут иметь тот же эффект:
++x = Q % 10; // LHS evaluated first
Q = ++x % 10; // RHS evaluated first
Теперь, это мое чтение стандарта. Хотя у меня есть достаточный объем опыта в декодировании сложных документов, может быть что-то, что я пропустил - я так не думаю, потому что у всех нас было оживленное обсуждение здесь, чтобы добраться до этого момента:-), и я думаю, что мы Все они установили соответствующие разделы.
Но, независимо от того, была ли она определена или нет, приличные кодеры не должны писать такой код. Это было долгое время, когда дни с низкой памятью/небольшим хранением PDP minis, это время, когда мы написали наш код для чтения.
Если вы хотите увеличивать, то используйте modulo, используйте x = (x + 1) % 10
, если только для того, чтобы упростить понимание для следующего плохого Джо, читающего этот код.
TL; DR: он определен корректно, поскольку x
гарантированно увеличивается до назначения.
[intro.execution]/15:
За исключением тех случаев, когда отмечено, оценки операндов отдельных операторов и подвыражения отдельных выражений не подвержены.
Однако, [expr.ass]/1 что-то замечает:
Во всех случаях назначение упорядочивается после вычисления значения правого и левого операндов, а перед вычислением значения выражение присваивания.
Таким образом, это составляет исключение из первой цитаты. Более того, как указано в [expr.pre.incr] 1++x
эквивалентно x += 1
, что также покрывается приведенной выше цитатой: Уступка упорядочивается перед вычислением значения. Таким образом, для ++x
приращение секвенируется перед вычислением значения.
Учитывая это, нетрудно видеть, что в ++x %= 10
приращение выполняется до назначения.
Таким образом, побочный эффект приращения секвенируется перед побочным эффектом присваивания, и поэтому все связанные с ним побочные эффекты секвенированы.
Чтобы выразить это по-другому, стандарт накладывает следующие последовательности:
++x
и 10
оцениваются, порядок которых не зависит от последовательности, но 10
является просто литералом, поэтому здесь не имеет значения.
++x
оценивается:
x
увеличивается.x
. x
принимается по модулю 10
и присваивается x
.Следовательно
Если побочный эффект скалярного объекта не зависит от другой побочный эффект на том же скалярном объекте или вычисление значения используя значение одного и того же скалярного объекта, поведение undefined.
не применяется в качестве , побочные эффекты и вычисления значений секвенированы.
1 Не [expr.post.incr], который был бы для приращения постфикса!
Посмотрим на унарный оператор инкремента:
5.3.2 Приращение и уменьшение [expr.pre.incr]
1 Операнд префикса
++
изменяется путем добавления 1 или установкиtrue
, если онbool
(это использование устарело). Операнд должен быть модифицируемым значением lvalue. Тип операнда должен быть арифметическим типом или указателем на полностью определенный тип объекта. Результатом является обновленный операнд; это lvalue, и это бит-поле, если операндом является бит-поле. Еслиx
не относится к типуbool
, выражение++x
эквивалентноx+=1
.
[...]
Таким образом, все оценки и побочные эффекты, относящиеся к этому унарному оператору, запланированы до его значения и, следовательно, не могут вызвать хаос.
Все, что осталось, оценивают %= 10
на этом lvalue. Только оценка константы может быть параллельной (что не могло бы навредить), остальное строго упорядочивается после всего остального.
Я собираюсь предложить альтернативный ответ, не цитируя хорошую книгу, так как я думаю, что переписывание ее немного делает ее очевидной.
++x %= 10; // as stated
x += 1 %= 10; // re-write the 'sugared' ++x
Это делает его достаточно ясным в моих глазах. Как мы знаем, результат присваивания (который, если мы действительно хотим, все еще "приглушенный" +=
сводится к) сам по себе является lvalue, поэтому не должно быть никаких сомнений в том, что путем дальнейшего сокращения выражение выражается следующим образом:
(x = x+1) %= 10 // += -> =1+
x = (x+1) % 10 // assignment returns reference to incremented x
В выражении
++x %= 10;
самая запутанная часть состоит в том, что x
дважды изменяется между двумя точками последовательности, один раз префикс ++
и один раз путем присвоения результата. Это создает впечатление, что вышеупомянутое выражение вызывает поведение undefined, как только мы узнали в старом С++, что
Между предыдущей и следующей точкой последовательности скалярный объект должен иметь значение, которое его значение хранится не более одного раза путем оценки выражения.
В С++ 11 правило отличается (речь идет о секвенировании вместо точек последовательности!):
Если побочный эффект на скалярном объекте несеквенный относительно другого побочного эффекта на том же скалярном объекте или вычисления значения с использованием значения одного и того же скалярного объекта, поведение undefined.
Как мы уже знаем, ++x
- это выражение будет оцениваться только один раз (и даст lvalue), потому что
Поведение выражения формы
E1 op = E2
эквивалентноE1 = E1 op E2
, за исключением того, чтоE1
оценивается только один раз.
а также знать, что оценка операндов ++x
и 10
будет выполняться перед вычислением результата оператора %=
по стандарту:
Вычисления значений операндов оператора секвенируются перед вычислением значения результата оператора.
Вывод:
++x
будет оцениваться только один раз, давая lvalue и только после этого будет выполняться операция %=
.
Это означает, что обе модификации в x
секвенированы, и указанное выражение хорошо определено.
Ну, это определено. Как поведение undefined:
§1.9/15:
Если побочный эффект на скалярном объекте не влияет на какой-либо другой побочный эффект на один и тот же скалярный объект или вычисление значения, используя значение одного и того же скалярного объекта, поведение undefined.
Здесь есть два побочных эффекта.
Учитывая выражение ++x %= 10;
, перемещающееся слева направо, мы имеем:
(x+1)
=
, как в x = x + 1
), например. побочный эффект по §1.9/12%=
), которая сама имеет как вычисление значения ('%
'), так и побочный эффект объекта-модификации ('=
') (там же, из 1,2) на один и тот же скалярный объект ('x
').Два подвыражения в полном выражении не имеют последовательности относительно друг друга. Хотя мы начали с чтения слева направо, они неопределенно секвенированы, поэтому в §1.9/13 явно нет частичного резервирования:
При любых двух оценках A и B, если A секвенирован до B, то выполнение A должно предшествовать исполнению B. Если A не секвенируется до того, как B и B не секвенированы до A, тогда A и B являются unsequenced.
Итак, UDB.
Приращение префикса (++ x) имеет наивысший приоритет при присвоении модуля (% =). Утверждение: ++ x% = 10; может быть выражено следующим образом:
++x;
x%= 10;