c++ language-lawyer undefined-behavior evaluation

¿++ x%=10 está bien definido en C++?



language-lawyer undefined-behavior (7)

Mientras navegaba por el código de algún proyecto, me encontré con la siguiente declaración:

++x %= 10;

¿Esta declaración está bien definida en C ++ o cae en la misma categoría que

a[i] = i++

?


Bueno, está definido . Como comportamiento indefinido:

§1.9 / 15:
Si un efecto secundario sobre un objeto escalar no está secuenciado en relación con otro efecto secundario sobre el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento es indefinido.

Aquí hay dos efectos secundarios no secuenciados.

Dada la expresión ++x %= 10; moviéndose de izquierda a derecha, tenemos:

  1. un cálculo de valor (x+1)
  2. modificar un objeto ( = , como en x = x + 1 ), por ejemplo, un efecto secundario según §1.9 / 12
  3. Una operación secuenciada indeterminadamente ( %= ) que tiene un cálculo de valor ('' % '') y un efecto secundario de modificación de objeto ('' = '') ( ibid de 1,2 ) en el mismo objeto escalar ('' x '') .

Las dos subexpresiones en la expresión completa no están secuenciadas entre sí . Aunque comenzamos leyéndolo de izquierda a derecha, estos están secuenciados de manera indeterminada , por lo que no hay explícitamente un rescate de orden parcial de §1.9 / 13:

Dadas cualesquiera dos evaluaciones A y B , si A se secuencia antes de B , entonces la ejecución de A precederá a la ejecución de B. Si A no está secuenciado antes de B y B no está secuenciado antes de A , entonces A y B no están secuenciados .

Entonces, UDB.


El incremento de prefijo (++ x) tiene mayor prioridad por la asignación de módulo (% =). La declaración: ++ x% = 10; se puede expresar como:

++x; x%= 10;


En la expresion

++x %= 10;

La parte más confusa es que x se modifica dos veces entre dos puntos de secuencia, una por prefijo ++ y otra por asignación de resultado. Esto da la impresión de que la expresión anterior invoca un comportamiento indefinido como una vez que aprendimos en C ++ antiguo que

Entre el punto de secuencia anterior y el siguiente, un objeto escalar tendrá su valor almacenado modificado como máximo una vez por la evaluación de una expresión.

En C ++ 11, la regla es diferente (¡se trata de secuenciar en lugar de puntos de secuencia !):

Si un efecto secundario sobre un objeto escalar no está secuenciado en relación con otro efecto secundario sobre el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento es indefinido.

Como ya sabemos que ++x es la expresión anterior, se evaluará solo una vez (y dará un valor l), porque

C ++ 11: 5.17

El comportamiento de una expresión de la forma E1 op = E2 es equivalente a E1 = E1 op E2 excepto que E1 se evalúa solo una vez .

y también sepa que la evaluación de los operandos ++x y 10 tendrá lugar antes del cálculo del resultado de %= operator como estándar:

Los cálculos del valor de los operandos de un operador se secuencian antes del cálculo del valor del resultado del operador.

Conclusión:

++x se evaluará solo una vez dando un valor l y solo después se realizará la operación %= . Esto significa que ambas modificaciones a x están secuenciadas y la expresión anterior está bien definida.


Según C ++ 11 1.9 Program execution /15 :

Excepto donde se indique, las evaluaciones de operandos de operadores individuales y de subexpresiones de expresiones individuales no tienen secuencia.

Si un efecto secundario sobre un objeto escalar no está secuenciado en relación con otro efecto secundario sobre el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento es indefinido.

En este caso, creo que ++x es un efecto secundario y x %= 10 es un cálculo de valor, por lo que pensaría que sería un comportamiento indefinido. Sin embargo, la sección de asignación ( 5.17 /1 ) tiene esto que decir (mi negrita):

En todos los casos, la asignación se secuencia después del cálculo del valor de los operandos derecho e izquierdo, y antes del cálculo del valor de la expresión de asignación.

Por lo tanto, eso significa que ambos lados se secuencian antes de la asignación y antes de que el resultado de la asignación esté disponible. Y dado que el estándar también establece ( 5.17 /7 ) que x OP = y es idéntico a x = x OP y pero con x solo evaluado una vez, resulta que este es un comportamiento bien definido, ya que es equivalente a:

++x = Q % 10; // where Q is the value from ++x, not evaluated again.

La única pregunta que queda es qué lado de la tarea se evalúa ya que no están secuenciados. Sin embargo, no creo que importe en este caso ya que ambos tendrán el mismo efecto:

++x = Q % 10; // LHS evaluated first Q = ++x % 10; // RHS evaluated first

Ahora, esa es mi lectura del estándar. Si bien tengo una buena experiencia en la decodificación de documentos complejos, puede que haya algo que me haya perdido, no lo creo porque todos hemos tenido una discusión animada aquí para llegar a este punto :-) y creo que Todos hemos establecido las secciones pertinentes.

Pero, independientemente de si está bien definido o no, los codificadores decentes no deberían escribir código como ese. Ha pasado mucho tiempo desde los días de poca memoria / pequeño almacenamiento de los minis PDP, ya es hora de que escribamos nuestro código para que sea legible.

Si desea aumentar, tome el módulo, use x = (x + 1) % 10 , aunque solo sea para que sea más fácil de entender para el próximo pobre Joe que lee ese código.


Veamos el operador de incremento unario:

5.3.2 Incremento y decremento [expr.pre.incr]

1 El operando del prefijo ++ se modifica agregando 1, o se establece en true si es bool (este uso está en desuso). El operando será un valor modificable. El tipo del operando será un tipo aritmético o un puntero a un tipo de objeto completamente definido. El resultado es el operando actualizado; es un valor de l , y es un campo de bits si el operando es un campo de bits. Si x no es de tipo bool , la expresión ++x es equivalente a x+=1 .
[...]

Por lo tanto, todas las evaluaciones y efectos secundarios relacionados con ese operador unario se programan antes de su valor y, por lo tanto, no pueden causar estragos.

Todo lo que queda es evaluar %= 10 en ese valor. Solo evaluar la constante podría ser concurrente (lo que posiblemente no podría causar ningún daño), el resto está estrictamente secuenciado después de todo lo demás.


Voy a ofrecer una respuesta alternativa, sin citar el buen libro, ya que creo que reescribirlo un poco lo hace obvio.

++x %= 10; // as stated x += 1 %= 10; // re-write the ''sugared'' ++x

Esto lo deja bastante claro en mis ojos. Como sabemos, el resultado de la asignación (que si realmente queremos, el todavía ''azucarado'' += reduce a) es en sí mismo un valor, por lo que no debería haber ninguna duda de que mediante una reducción adicional la expresión es:

(x = x+1) %= 10 // += -> =1+ x = (x+1) % 10 // assignment returns reference to incremented x


TL; DR : Está bien definido porque se garantiza que x se incrementará antes de la asignación.

Algunos C ++ 11 standardese

[intro.execution] / 15:

Excepto donde se indique , las evaluaciones de operandos de operadores individuales y de subexpresiones de expresiones individuales no tienen secuencia.

Sin embargo, [expr.ass] / 1 nota algo:

En todos los casos, la asignación se secuencia después del cálculo del valor de los operandos derecho e izquierdo , y antes del cálculo del valor de la expresión de asignación.

Entonces esto constituye una excepción a la primera cita. Además, como se indica en [expr.pre.incr] 1 , ++x es equivalente a x += 1 , que también está cubierto por la cita anterior: la asignación se secuencia antes del cálculo del valor. Por lo tanto, para ++x el incremento se secuencia antes del cálculo del valor.

Teniendo eso en cuenta, no es difícil ver que en ++x %= 10 , el incremento se realiza antes de que se realice la asignación.
Por lo tanto, el efecto secundario incremental se secuencia antes del efecto secundario de asignación y, por lo tanto, se secuencian todos los efectos secundarios involucrados.

Para decirlo de otra manera, el estándar impone la siguiente secuencia:

  • ++x y 10 son evaluados, el orden de los cuales no está secuenciado, pero 10 es solo un literal, por lo que no es relevante aquí.
    • ++x se evalúa:
      • Primero, el valor de x se incrementa.
      • Luego se realiza el cálculo del valor y obtenemos un valor l que se refiere a x .
  • La tarea está hecha. El valor actualizado de x se toma el módulo 10 y se asigna a x .
  • Puede seguir el cálculo del valor de la tarea, que se secuencia claramente después de la tarea.

Por lo tanto

Si un efecto secundario sobre un objeto escalar no está secuenciado en relación con otro efecto secundario sobre el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento es indefinido.

no se aplica ya que los efectos secundarios y los cálculos de valores están secuenciados .

1 ¡ No [expr.post.incr] que sería para el incremento de postfix!