¿Por qué `i=i+++1` es un comportamiento indefinido en C++ 11?
counter++ python (3)
Estoy leyendo el borrador n3290 del estándar C ++ 11 (lo más cerca que pude llegar al texto estándar real), y noté que i = i++ + 1;
Produce un comportamiento indefinido. He visto preguntas similares antes, pero fueron respondidas en términos de estándares más antiguos (puntos de secuencia). El nuevo estándar introduce en su lugar el concepto de secuenciación antes / después de la relación entre las ejecuciones de expresión y subexpresión.
1.9 13 Secuenciado antes es una relación asimétrica, transitiva y por pares entre las evaluaciones ejecutadas por un solo hilo (1.10), que induce un orden parcial entre esas evaluaciones. Dadas las dos evaluaciones A y B, si A está secuenciada antes de B, entonces la ejecución de A precederá a la ejecución de B. Si A no está secuenciada antes de que B y B no estén secuenciadas antes de A, entonces A y B no tienen secuencia. [Nota: La ejecución de evaluaciones no secuenciales puede superponerse. —Descripción final] Las evaluaciones A y B se secuencian de forma indeterminada cuando A se secuencia antes de que B o B se secuencian antes de A, pero no se especifica cual. [Nota: las evaluaciones de secuencia indeterminada no pueden superponerse, pero cualquiera de las dos podría ejecutarse primero. "Nota final"
1.9 14 Cada cálculo de valor y efecto secundario asociado con una expresión completa se secuencian antes de cada cálculo de valor y efecto secundario asociado con la siguiente expresión completa a evaluar.
1.9 15 Excepto cuando se indique lo contrario, las evaluaciones de los operandos de operadores individuales y de las subexpresiones de expresiones individuales no tienen secuencia. [Nota: En una expresión que se evalúa más de una vez durante la ejecución de un programa, las evaluaciones no secuenciales e indeterminadas de sus subexpresiones no deben realizarse de manera consistente en diferentes evaluaciones. —Endente final] 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 de otro lado en el mismo objeto escalar o un cálculo de valor utilizando el valor del mismo objeto escalar, el comportamiento no está definido.
[ Example:
void f(int, int);
void g(int i, int* v) {
i = v[i++]; // the behavior is undefined
i = 7, i++, i++; // i becomes 9
i = i++ + 1; // the behavior is undefined
i = i + 1; // the value of i is incremented
f(i = -1, i = -1); // the behavior is undefined
}
—end example ]
La forma en que lo entiendo, funciona así:
-
operator=
tiene dos expresiones de operando: haciendo referencia ai
ei++ + 1
, ambas no se encuentran entre sí. El segundo tiene efectos secundarios eni
, pero el primero me parece que no tiene efectos secundarios ni se utiliza en el cálculo de valores (¿o es la referencia tomando "un cálculo de valores utilizando el valor del mismo objeto escalar"? ¿Realmente depende? en el valor almacenado en i? no lo creo), por lo que no es un comportamiento indefinido; -
operator=
ejecución se secuencia después de la evaluación de ambos operandos. Tiene un efecto secundario eni
, pero está bien secuenciado en referencia a ambos operandos, por lo que no es un comportamiento definido; -
i++ + 1
es obviamente un comportamiento definido.
¿Estoy equivocado acerca de algo aquí? ¿O es esta línea de comportamiento indefinido por alguna otra razón?
PD. Estándar en realidad dice
Los cálculos de valores de los operandos de un operador se secuencian antes del cálculo de valores del resultado del operador.
, y no menciona los efectos secundarios en este contexto en absoluto. Sin embargo, la relación de secuenciación se define solo entre evaluaciones de expresión y evaluación = valor computacional + efectos secundarios. Entonces, o bien tengo que asumir que este borrador es inconsistente aquí, o asumir que en esta línea significaron evaluación en lugar de cálculo de valor. ¿O me equivoco aquí?
EDITAR:
Supongo que me responderé aquí mismo, pero esa fue la razón de mi confusión:
5 1 Una expresión es una secuencia de operadores y operandos que especifica un cálculo. Una expresión puede resultar en un valor y puede causar efectos secundarios.
Así que los operandos de los operadores no son subexpresiones. Por lo tanto, solo el cálculo de valores para i = i++ + 1;
completo i = i++ + 1;
está secuenciado, y ninguna mención de secuenciación de efectos secundarios se hace de forma estándar. Esa es la razón por la que está indefinido.
Tenga en cuenta que si por ejemplo. operator=
estaba sobrecargado para un tipo dado (por lo que sería una llamada de función implícita) no sería un comportamiento indefinido, ¿verdad?
Es un "comportamiento indefinido", no "no especificado". No definido significa que la máquina tiene permitido hacer cualquier cosa, incluida la salida de un programa vacío, la terminación aleatoria o la explosión. Por supuesto, un valor sutilmente inesperado al migrar a otra plataforma es un resultado más probable.
El comportamiento indefinido se aplica a cualquier caso en el que dos efectos secundarios se apliquen al mismo escalar sin ser secuenciados entre sí. En este caso, los efectos secundarios son idénticos (ambos incrementan i
desde su valor original antes de la expresión), pero por la letra del estándar, se combinan para producir UB.
Los efectos secundarios no tienen secuencia porque, aparte de,, ?:
, ||
y &&
, los operadores no definen las reglas de secuencia en términos tales como C ++ 11 §5.15 / 2:
Si se evalúa la segunda expresión, cada cálculo de valor y efecto secundario asociado con la primera expresión se secuencia antes de cada cálculo de valor y efecto secundario asociado con la segunda expresión.
Los operadores de asignación definen una regla de secuencia especial, §5.17 / 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 no ayuda a i = i ++ + 1
porque el efecto secundario de i ++
no es parte de ningún cálculo de valor.
Estás confundiendo el cálculo de valores con la resolución de los efectos secundarios. Si bien el valor de i++
debe calcularse antes de la asignación, nada secuencia el efecto secundario (la modificación de i
) con respecto a la asignación que no sea la finalización de la expresión completa.
Para un ejemplo contrastante, eche un vistazo al operador de coma: "Cada cálculo de valor y efecto secundario asociado con la expresión izquierda se secuencia antes de cada cálculo de valor y efecto secundario asociado con la expresión correcta". Observe cómo el cálculo del valor y los efectos secundarios se mencionan por separado. No hay tal regla para la asignación.
en C ++ 03 tanto i = ++i + 1;
y i = i++ + 1
no están bien definidos.
Pero en C ++ 11, i = ++i + 1
queda bien definido. pero i=i++ + 1
todavía no lo es.
Mire el contenido en este como para obtener detalles. Reglas de secuenciación y ejemplo en desacuerdo