Является ли ++ x% = 10 корректным в С++?

При просмотре кода какого-либо проекта я столкнулся с следующим утверждением:

++x %= 10;

Является ли этот оператор хорошо определенным в С++ или он попадает в ту же категорию, что и

a[i] = i++

?

Ответ 1

В соответствии с С++ 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, если только для того, чтобы упростить понимание для следующего плохого Джо, читающего этот код.

Ответ 2

TL; DR: он определен корректно, поскольку x гарантированно увеличивается до назначения.


Некоторые С++ 11 standardese

[intro.execution]/15:

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

Однако, [expr.ass]/1 что-то замечает:

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

Таким образом, это составляет исключение из первой цитаты. Более того, как указано в [expr.pre.incr] 1++x эквивалентно x += 1, что также покрывается приведенной выше цитатой: Уступка упорядочивается перед вычислением значения. Таким образом, для ++x приращение секвенируется перед вычислением значения.

Учитывая это, нетрудно видеть, что в ++x %= 10 приращение выполняется до назначения.
Таким образом, побочный эффект приращения секвенируется перед побочным эффектом присваивания, и поэтому все связанные с ним побочные эффекты секвенированы.

Чтобы выразить это по-другому, стандарт накладывает следующие последовательности:

  • ++x и 10 оцениваются, порядок которых не зависит от последовательности, но 10 является просто литералом, поэтому здесь не имеет значения.
    • ++x оценивается:
      • Во-первых, значение x увеличивается.
      • Затем вычисляется значение, и мы получаем значение lvalue, относящееся к x.
  • Назначение выполнено. Обновленное значение x принимается по модулю 10 и присваивается x.
  • После вычисления будет выполняться вычисление значения присваивания.

Следовательно

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

не применяется в качестве , побочные эффекты и вычисления значений секвенированы.


1 Не [expr.post.incr], который был бы для приращения постфикса!

Ответ 3

Посмотрим на унарный оператор инкремента:

5.3.2 Приращение и уменьшение [expr.pre.incr]

1 Операнд префикса ++ изменяется путем добавления 1 или установки true, если он bool (это использование устарело). Операнд должен быть модифицируемым значением lvalue. Тип операнда должен быть арифметическим типом или указателем на полностью определенный тип объекта. Результатом является обновленный операнд; это lvalue, и это бит-поле, если операндом является бит-поле. Если x не относится к типу bool, выражение ++x эквивалентно x+=1.
[...]

Таким образом, все оценки и побочные эффекты, относящиеся к этому унарному оператору, запланированы до его значения и, следовательно, не могут вызвать хаос.

Все, что осталось, оценивают %= 10 на этом lvalue. Только оценка константы может быть параллельной (что не могло бы навредить), остальное строго упорядочивается после всего остального.

Ответ 4

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

++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

Ответ 5

В выражении

++x %= 10;  

самая запутанная часть состоит в том, что x дважды изменяется между двумя точками последовательности, один раз префикс ++ и один раз путем присвоения результата. Это создает впечатление, что вышеупомянутое выражение вызывает поведение undefined, как только мы узнали в старом С++, что

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

В С++ 11 правило отличается (речь идет о секвенировании вместо точек последовательности!):

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

Как мы уже знаем, ++x - это выражение будет оцениваться только один раз (и даст lvalue), потому что

С++ 11: 5.17

Поведение выражения формы E1 op = E2 эквивалентно E1 = E1 op E2 , за исключением того, что E1 оценивается только один раз.

а также знать, что оценка операндов ++x и 10 будет выполняться перед вычислением результата оператора %= по стандарту:

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

Вывод:

++x будет оцениваться только один раз, давая lvalue и только после этого будет выполняться операция %=. Это означает, что обе модификации в x секвенированы, и указанное выражение хорошо определено.

Ответ 6

Ну, это определено. Как поведение 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.

Ответ 7

Приращение префикса (++ x) имеет наивысший приоритет при присвоении модуля (% =). Утверждение: ++ x% = 10; может быть выражено следующим образом:

++x;
x%= 10;