c++ c++11 undefined-behavior language-lawyer c++03

¿En qué versiones del estándar de C++ "(i+= 10)+= 10" tiene un comportamiento indefinido?



c++11 undefined-behavior (3)

En C ++, ¿tiene el siguiente comportamiento indefinido:

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

Hubo cierto debate al respecto en los comentarios a mi respuesta a ¿Cuál es el resultado de + = en C y C ++? La sutileza aquí es que la respuesta predeterminada parece ser "sí", mientras que parece que la respuesta correcta es "depende de la versión del estándar C ++".

Si no depende de la versión de la norma, explique dónde está la UB y dónde no.


En C ++ 11, la expresión está bien definida y dará como resultado i == 20 .

Desde [expr.ass]/1 :

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.

Esto significa que la asignación i+=1 se secuencia antes del cálculo de valor del lado izquierdo de (i+=10)+=10 , que a su vez se secuencia antes de la asignación final a i .

En C ++ 03, la expresión tiene un comportamiento indefinido, porque causa que i sea ​​modificado dos veces sin un punto de secuencia intermedio.


Tal vez. Depende de la versión de C ++.

En C ++ 03, es un UB obvio, no hay punto de secuencia intermedio entre las asignaciones.

En C ++ 11, como explica Mankarse, ya no está indefinido: la asignación compuesta entre paréntesis está secuenciada antes que la externa, por lo que está bien.


tl; dr : la secuencia de las modificaciones y lecturas realizadas en (i+=10)+=10 está bien definida tanto en C ++ 98 como en C ++ 11; sin embargo, en C ++ 98 esto no es suficiente para realizar el comportamiento definido.

En C ++ 98, las modificaciones múltiples al mismo objeto sin un punto de secuencia intermedio dan como resultado un comportamiento indefinido, incluso cuando el orden de esas modificaciones está bien especificado. Esta expresión no contiene ningún punto de secuencia, por lo que el hecho de que consista en dos modificaciones es suficiente para que su comportamiento no esté definido.

C ++ 11 no tiene puntos de secuencia y solo requiere que las modificaciones de un objeto se ordenen una con respecto a la otra y a lecturas del mismo objeto para producir un comportamiento definido.

Por lo tanto, el comportamiento no está definido en C ++ 98 pero está bien definido en C ++ 11.

C ++ 98

Cláusula C ++ 98 [expr] 5 p4

Excepto cuando se indique lo contrario, el orden de evaluación de operandos de operadores individuales y las subexpresiones de expresión individual, y el orden en que se producen los efectos secundarios, no se especifica.

Cláusula C ++ 98 [expr.ass] 5.17 p1

El resultado de la operación de asignación es el valor almacenado en el operando izquierdo después de que la asignación ha tenido lugar; el resultado es un lvalue

Así que creo que se especifica el orden, sin embargo, no veo que eso solo sea suficiente para crear un punto de secuencia en el medio de una expresión. Y continuando con la cita de [expr] 5 p4:

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

Entonces, aunque se especifica el orden, me parece que esto no es suficiente para un comportamiento definido en C ++ 98.

C ++ 11

C ++ 11 elimina los puntos de secuencia para la idea mucho más clara de secuencia antes y secuencia después . El lenguaje de C ++ 98 se reemplaza por

C ++ 11 [intro.execution] 1.9 p15

Excepto donde se indique, las evaluaciones de operandos de operadores individuales y de subexpresiones de expresiones individuales no son secuenciadas. [...]

Si un efecto secundario en un objeto escalar no se está secuenciando en relación con otro efecto secundario en el mismo objeto escalar o con un cálculo de valor que utiliza el valor del mismo objeto escalar, el comportamiento no está definido.

C ++ 11 [expr.ass] 5.17 p1

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, si bien la orden no fue suficiente para definir el comportamiento en C ++ 98, C ++ 11 ha cambiado el requisito de tal manera que ordenarlo (es decir, secuenciar) es suficiente.

(Y me parece que la flexibilidad extra que ofrecen ''secuencia antes'' y ''secuencia después'' ha llevado a un lenguaje mucho más claro, consistente y bien especificado).

Me parece poco probable que cualquier implementación de C ++ 98 realmente haga algo sorprendente cuando la secuencia de operaciones está bien especificada, incluso si eso es insuficiente para producir un comportamiento técnicamente bien definido. Como ejemplo, la representación interna de esta expresión producida por Clang en el modo C ++ 98 tiene un comportamiento bien definido y hace lo esperado.