Orden de evaluación
El orden de evaluación de cualquier parte de cualquier expresión, incluyendo el orden de evaluación de los argumentos de función está no especificado (con algunas excepciones como se lista abajo). El compilador puede evaluar los operandos y otras subexpresiones en cualquier orden, y puede escoger otro orden cuando la misma expresión se evalúa de nuevo.
No existe un concepto de evaluación de izquierda a derecha o de derecha a izquierda en C++. Esto no debe confundirse con la asociatividad de los operadores de izquierda a derecha y de derecha a izquierda: la expresión a() + b() + c()
se analiza como (a() + b()) + c()
debido a la asociatividad de izquierda a derecha del operador +, pero la llamada de función a c
puede evaluarse primero, al último, o entre a()
o b()
en tiempo de ejecución:
#include <cstdio> int a() { return std::puts("a"); } int b() { return std::puts("b"); } int c() { return std::puts("c"); } void z(int, int, int) {} int main() { z(a(), b(), c()); // todas las 6 permutaciones de salida se permiten return a() + b() + c(); // todas las 6 permutaciones de salida se permiten }
Posible salida:
b c a c a b
Contenido |
[editar] Reglas de secuenciado-antes/secuenciada-antes (desde C++11)
El término secuenciado-antes se aplica en su mayoría a evaluaciones, donde usaremos el término en femenino, secuenciada-antes. Donde el género de la aplicación sea masculino, usaremos secuenciado-antes. El término en inglés es sequenced-before, que no tiene género. Esta distinción es importante cuando hablemos del ordenamiento de memoria en otras partes de la documentación. Aunque una traducción sin género podría ser "se secuencia antes", solamente se puede usar en tiempo presente.
[editar] Definiciones
[editar] Evaluación de expresiones
La evaluación de cada expresión incluye:
- Cálculos de valor: cálculo del valor devuelto por la expresión. Esto puede conllevar la determinación de la identidad del objeto (evaluación glvalue, p. ej., si la expresión devuelve una referencia a algún objeto) o la lectura de un valor asignado a un objeto previamente (evaluación prvalue, p. ej., si la expresión devuelve un número o cualquier otro valor).
- Inicio de los efectos secundarios: acceso (de lectura o escritura) a un objeto designado por un glvalue volátil, modificación (escritura) de un objeto, llamada a una función de E/S de la biblioteca, o llamada a una función que hace cualquiera de esas operaciones.
[editar] Ordenamiento
Secuenciada-antes es una relación asimétrica, transitiva y de pares entre evaluaciones dentro del mismo hilo.
- Si A está secuenciada antes que B, entonces la evaluación de A se debe completar antes de comenzar la evaluación de B.
- Si A no está secuenciada antes que B y B está secuenciada antes que A, entonces la evaluación de B se debe completar antes del comienzo de la evaluación de A.
- Si A no está secuenciada antes que B y B no está secuenciada antes que A, existen dos posibilidades:
- las evaluaciones de A y B no son secuenciadas: se pueden realizar en cualquier orden y superponerse (dentro de un mismo hilo de ejecución, el compilador puede intercalar las intrucciones de CPU que componen a A y B)
- las evaluaciones de A y B están secuenciadas indeterminadamente: se pueden realizar en cualquier orden pero no se pueden superponer: o se completa A antes que B, o B antes que A. El orden podría ser opuesto la próxima vez que se evalúe la misma expresión.
[editar] Reglas
- un operando sin evaluar;
- una expresión constante;
|
(desde C++20) |
- un inicializador completo, incluyendo algunas expresiones constituyentes separadas por comas;
- la llamada al destructor generada al final de la duración de un objeto no temporal;
- una expresión que no es parte de otra expresión completa (como la instrucción de expresión completa, la expresión de control de un bucle for/while, la expresión condicional de if/switch, la expresión en una instrucción return, etc.),
La regla 11 tiene una excepción: las llamadas a función hechas por un algoritmo de la biblioteca estándar ejecutando bajo la política de ejecuciónstd::execution::par_unseq son sin secuenciar y pueden ser intercaladas arbitrariamente. | (desde C++17) |
13) Cuando se retorna de una función, la inicialización de copia del temporal que es el resultado de la evaluación de la llamada a la función es secuenciada antes de la destrucción de todos los temporales al final del operando de la instrucción return, que, a su vez, es secuenciada antes de la destrucción de las variables locales del bloque que encierra la instrucción return. |
(desde C++14) |
14) En una expresión de llamada a función, la expresión que nombra la función es secuenciada antes que cualquier expresión de argumento y argumento predeterminado.
15) En una llamada a función, el cálculo de valores y los efectos secundarios de la incialización de cualquier parámetro son secuenciados indeterminadamente respecto al cálculo de valores y efectos secundarios de cualquier otro parámetro.
16) Cada operador sobrecargado sigue las reglas de secuenciación del operador integrado que sobrecarga cuando se llama mediante la notación del operador.
17) En una expresión de subíndice
E1[E2] , cada cálculo de valor y efecto secundario de E1 es secuenciado antes que cualquier cálculo de valor y efecto secundario de E2.18) En una expresión de puntero a miembro
E1.*E2 o E1->*E2 , cada cálculo de valor y efecto secundario de E1 es secuenciado antes que cualquier cálculo de valor y efecto secundario de E2 (aunque el tipo dinámico de E1 no tenga el miembro al que se refiere E2)19) En una expresión con el operador de desplazamiento
E1<<E2 y E1>>E2 , cada cálculo de valor y efecto secundario de E1 es secuenciado antes que cualquier cálculo de valor y efecto secundario de E220) En cualquier expresión de asignación simple
E1=E2 y asignación compuesta E1@=E2 , cada cálculo de valor y efecto secundario de E2 es secuenciado antes que cualquier cálculo de valor y efecto secundario de E1.21) Cada expresión en una lista separada por comas de expresiones en un inicializador entre paréntesis se evaluá como una llamada a función (secuenciado indeterminadamente).
|
(desde C++17) |
[editar] Comportamiento indefinido
1) Si un efecto secundario en un objeto escalar no es secuenciado relativo a otro efecto secundario sobre el mismo objeto, el comportamiento es indefinido.
i = ++i + 2; // comportamiento indefinido hasta C++11
i = i++ + 2; // comportamiento indefinido hasta C++17
f(i = -2, i = -2); // comportamiento indefinido hasta C++17
f(++i, ++i); // comportamiento indefinido hasta C++17, sin especificar desde C++17
i = ++i + i++; // comportamiento indefinido
2) Si un efecto secundario sobre un objeto escalar no es secuenciado relativo a un cálculo de valor usando el valor del mismo objeto, el comportamiento es indefinido.
cout << i << i++; // comportamiento indefinido hasta C++17
a[i] = i++; // comportamiento indefinido hasta C++17
n = ++i + i; // comportamiento indefinido
[editar] Reglas de punto de secuencia (hasta C++11)
[editar] Definiciones
La evaluación de una expresión puede producir efectos secundarios, que son: acceso a un objeto designado por un lvalue volátil, modificar un objeto, llamada a una función de la biblioteca de E/S, o llamada a una función que realiza alguna de esas operaciones.
Un punto de secuencia es un punto en la secuencia de ejecución donde todos los efectos secundarios de la evaluación anterior en la secuencia están completos, y no se iniciaron efectos secundarios de las evaluaciones posteriores.
[editar] Reglas
1) Hay un punto de secuencia al final de cada expresión completa (normalmente, en el punto y coma).
2) Al llamar a una función (ya sea que la función sea inline o no, y si se usó o no la sintaxis de llamada a función), hay un punto de secuencia después de la evaluación de todos los argumentos de la función (si corresponde) que tiene lugar antes de la ejecución de cualquier expresión o declaración en el cuerpo de la función.
3) Hay un punto de secuencia después de la copia de un valor devuelto de una función y antes de la ejecución de cualquier expresión fuera de la función.
4) Una vez que comienza la ejecución de una función, no se evalúa ninguna expresión de la función de llamada hasta que se haya completado la ejecución de la función llamada (las funciones no se pueden intercalar).
5) En la evaluación de cada una de las siguientes cuatro expresiones, utilizando los operadores integrados (no sobrecargados), hay un punto de secuencia después de la evaluación de la expresión a
.
a && b
a || b
a ? b : c
a , b
[editar] Comportamiento indefinido
1) Entre el punto de secuencia anterior y el siguiente, un objeto escalar debe tener su valor almacenado modificado a lo sumo una vez por la evaluación de una expresión, de lo contrario el comportamiento es indefinido.
i = ++i + i++; // comportamiento indefinido
i = i++ + 1; // comportamiento indefinido (hasta C++17)
i = ++i + 1; // comportamiento indefinido (hasta C++11)
++ ++i; // comportamiento indefinido (hasta C++11)
f(++i, ++i); // comportamiento indefinido (hasta C++17)
f(i = -1, i = -1); // comportamiento indefinido (hasta C++17)
2) Entre el punto de secuencia anterior y el siguiente, se debe acceder al valor anterior de un objeto escalar que se modifica por la evaluación de la expresión, solo para determinar el valor que se almacenará. Si se accede de alguna otra manera, el comportamiento es indefinido.
cout << i << i++; // comportamiento indefinido (hasta C++17)
a[i] = i++; // comportamiento indefinido (hasta C++17)
[editar] Informes de defectos
Los siguientes informes de defectos de cambio de comportamiento se aplicaron de manera retroactiva a los estándares de C++ publicados anteriormente.
ID | Aplicado a | Comportamiento según lo publicado | Comportamiento correcto |
---|---|---|---|
CWG 1885 | C++14 | La secuencia de destrucción de variables automáticas en el retorno de la función no era explícita | Se añadieron reglas de secuenciación |
[editar] Referencias
- El estándar C++11 (ISO/IEC 14882:2011):
- 1.9 Ejecución de programa [intro.execution]
- 5.2.6 Incremento y decremento [expr.post.incr]
- 5.3.4 Operador new [expr.new]
- 5.14 Operador lógico AND [expr.log.and]
- 5.15 Operador lógico OR [expr.log.or]
- 5.16 Operador condicional [expr.cond]
- 5.17 Operadores de asignación y asignación compuesta [expr.ass]
- 5.18 Operador coma [expr.comma]
- 8.5.4 Inicialización de lista [dcl.init.list]
[editar] Véase también
- Precedencia de operadores que define cómo se crean las expresiones a partir de su representación de código fuente.
Documentación de C para Orden de evaluación
|