c++ - Orden de evaluación y comportamiento indefinido.
c++11 sequence (2)
Hablando en el contexto del estándar C ++ 11 (que ya no tiene un concepto de puntos de secuencia, como ya sabe) quiero entender cómo se definen los dos ejemplos más simples.
int i = 0;
i = i++; // #0
i = ++i; // #1
Hay dos temas sobre SO que explican esos ejemplos dentro del contexto de C ++ 11. Here se dijo que #0
invoca a UB y #1
está bien definido. Here se dijo que ambos ejemplos están indefinidos. Esta ambigüedad me confunde mucho. Ya he leído esta reference bien estructurada tres veces, pero el tema parece demasiado complicado para mí.
.
Analicemos el ejemplo #0
: i = i++;
.
Las citas correspondientes son:
El cálculo del valor de los operadores incorporados de postincremento y postdecrement se secuencia antes de su efecto secundario.
El efecto secundario (modificación del argumento izquierdo) del operador de asignación integrado y de todos los operadores de asignación compuesta integrada se secuencia después del cálculo del valor (pero no los efectos secundarios) de los argumentos izquierdo y derecho, y se secuencia antes el cálculo del valor de la expresión de asignación (es decir, antes de devolver la referencia al objeto modificado)
Si un efecto secundario en un objeto escalar no tiene secuencia en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento no está definido.
A medida que lo entiendo, el efecto secundario del operador de asignación no se secuencia con los efectos secundarios de los argumentos de izquierda y derecha. Por lo tanto, el efecto secundario del operador de asignación no se secuencia con los efectos secundarios de i++
. Entonces #0
invoca un UB.
.
Analicemos el ejemplo #1
: i = ++i;
.
Las citas correspondientes son:
El efecto secundario de los operadores integrados de preincremento y predecremento se secuencia antes de su cálculo del valor (regla implícita debido a la definición como asignación compuesta)
El efecto secundario (modificación del argumento izquierdo) del operador de asignación integrado y de todos los operadores de asignación compuesta integrada se secuencia después del cálculo del valor (pero no los efectos secundarios) de los argumentos izquierdo y derecho, y se secuencia antes el cálculo del valor de la expresión de asignación (es decir, antes de devolver la referencia al objeto modificado)
Si un efecto secundario en un objeto escalar no tiene secuencia en relación con otro efecto secundario en el mismo objeto escalar, el comportamiento no está definido.
No puedo ver cómo este ejemplo es diferente del #0
. Esto parece ser un UB para mí por la misma razón que #0
. El efecto secundario de la asignación no se secuencia con el efecto secundario de ++i
. Parece ser una UB. El tema mencionado anteriormente dice que está bien definido. ¿Por qué?
.
Pregunta : ¿Cómo puedo aplicar reglas entre comillas para determinar la UB de los ejemplos? Una explicación tan simple como sea posible sería apreciada grandemente. ¡Gracias!
La diferencia clave es que ++i
se define como i += 1
, por lo que
i = ++i;
es lo mismo que:
i = (i += 1);
Dado que los efectos secundarios del operador +=
se secuencian antes del cálculo del valor del operador, la modificación real de i
in ++i
se secuencia antes de la asignación externa. Esto se deduce directamente de las secciones que cita: "El efecto secundario (modificación del argumento izquierdo) del operador de asignación integrado y de todos los operadores de asignación compuesta integrada se secuencia después del cálculo del valor (pero no de los efectos secundarios) de argumentos a la izquierda y derecha, y se secuencia antes del cálculo del valor de la expresión de asignación (es decir, antes de devolver la referencia al objeto modificado) "
Esto se debe al operador de asignación anidada; el operador de asignación (externo) solo impone una secuencia antes en el cálculo del valor de sus operandos, no en sus efectos secundarios. (Pero, por supuesto, no deshace la secuencia impuesta de otra manera).
Y como señala indirectamente, esto es nuevo para C ++ 11; Anteriormente, ambos estaban indefinidos. Las versiones anteriores de C ++ utilizaban puntos de secuencia, en lugar de secuenciarse antes, y no había ningún punto de secuencia en ninguno de los operadores de asignación. (Tengo la impresión de que la intención era que los operadores que dan como resultado un valor l tenga un valor que se secuencia después de cualquier efecto secundario. En C ++ anterior, la expresión *&++i
era un comportamiento indefinido; en C ++ 11, es Garantizado para ser el mismo que ++i
.)
Ya que sus citas no son directamente del estándar, intentaré dar una respuesta detallada con las partes relevantes del estándar. Las definiciones de "efectos secundarios" y "evaluación" se encuentran en el párrafo 1.9 / 12:
Acceder a un objeto designado por un glvalue volátil (3.10), modificar un objeto, llamar a una función de E / S de la biblioteca o llamar a una función que realiza cualquiera de esas operaciones son todos efectos secundarios , que son cambios en el estado del entorno de ejecución. La evaluación de una expresión (o una subexpresión) en general incluye tanto los cálculos de valores (incluida la determinación de la identidad de un objeto para la evaluación de glvalue como la obtención de un valor previamente asignado a un objeto para la evaluación de prvalue) y el inicio de efectos secundarios.
La siguiente parte relevante es el párrafo 1.9 / 15:
Excepto donde se indique, las evaluaciones de los operandos de los operadores individuales y de las subexpresiones de las expresiones individuales no tienen secuencia. [...] Los cálculos de valores de los operandos de un operador se secuencian antes del cálculo de valores del resultado del operador. Si un efecto secundario en un objeto escalar no tiene secuencia en relación con otro efecto secundario en el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento no está definido.
Ahora veamos, cómo aplicar esto a los dos ejemplos.
i = i++;
Esta es la forma de incremento postfix y encontrará su definición en el párrafo 5.2.6. La frase más relevante dice:
El cálculo del valor de la expresión ++ se secuencia antes de la modificación del objeto operando.
Para la expresión de asignación vea el párrafo 5.17. La parte relevante declara:
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.
Usando toda la información de arriba, la evaluación de toda la expresión es (¡este orden no está garantizado por la norma!):
- cálculo del valor de
i++
(lado derecho) - cálculo del valor de
i
(lado izquierdo) - modificación de
i
(efecto secundario de++
) - modificación de
i
(efecto secundario de=
)
Todas las garantías estándar son que los cálculos de valores de los dos operandos se secuencian antes del cálculo de valores de la expresión de asignación. Pero el cálculo del valor del lado derecho es solo "leer el valor de i
" y no modificar i
, las dos modificaciones (efectos secundarios) no se secuencian entre sí y obtenemos un comportamiento indefinido.
¿Qué pasa con el segundo ejemplo?
i = ++i;
La situación es bastante diferente aquí. Encontrará la definición de incremento de prefijo en el párrafo 5.3.2. La parte relevante es:
Si x no es de tipo bool, la expresión ++ x es equivalente a x + = 1.
Sustituyendo eso, nuestra expresión es equivalente a
i = (i += 1)
Al buscar el operador de asignación compuesta +=
en 5.17 / 7 obtenemos que i += 1
es equivalente a i = i + 1
excepto que i
solo se evalúa una vez. Por lo tanto, la expresión en cuestión finalmente se convierte en
i = (i = (i + 1))
Pero ya sabemos por arriba que el cálculo del valor de =
se secuencia después del cálculo del valor de los operandos y los efectos secundarios se secuencian antes de los cálculos del valor de =
. Entonces obtenemos un orden de evaluación bien definido:
- calcular el valor de
i + 1
(yi
- lado izquierdo de la expresión interna) (# 1) - iniciar el efecto secundario de inner
=
, es decir, modificar "i
" interno - calcular el valor de
(i = i + 1)
, que es el "nuevo" valor dei
- iniciar efecto secundario de exterior
=
, es decir, modificar "exterior"i
- calcular el valor de la expresión completa.
(# 1): Aquí, i
solo se evalúa una vez, ya que i += 1
es equivalente a i = i + 1
excepto que i
solo se evalúa una vez (5.17 / 7).