Namespaces
Variants

Order of evaluation

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Value categories
Order of evaluation
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

El orden de evaluación de cualquier parte de cualquier expresión, incluyendo el orden de evaluación de los argumentos de función es no especificado (con algunas excepciones listadas abajo). El compilador puede evaluar operandos y otras subexpresiones en cualquier orden, y puede elegir otro orden cuando la misma expresión es evaluada nuevamente.

No existe el concepto de evaluación de izquierda a derecha o de derecha a izquierda en C++. Esto no debe confundirse con la asociatividad de izquierda a derecha y de derecha a izquierda de los operadores: la expresión a ( ) + b ( ) + c ( ) se analiza como ( a ( ) + b ( ) ) + c ( ) debido a la asociatividad de izquierda a derecha del operator + , pero c ( ) podría 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());       // se permiten las 6 permutaciones de salida
    return a() + b() + c(); // se permiten las 6 permutaciones de salida
}

Salida posible:

b
c
a
c
a 
b

Contenidos

Reglas de "secuenciado antes" (desde C++11)

Evaluación de Expresiones

La evaluación de cada expresión incluye:

  • Cálculos de valor : cálculo del valor que devuelve la expresión. Esto puede implicar 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 del valor previamente asignado a un objeto (evaluación prvalue, p.ej. si la expresión devuelve un número u otro valor).
  • Iniciación de efectos secundarios : acceso (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 biblioteca, o llamada a una función que realice cualquiera de estas operaciones.

Ordenación

Secuenciado antes es una relación asimétrica, transitiva y de pares entre evaluaciones A y B dentro del mismo hilo.

  • Si A está secuenciado antes de B (o, equivalentemente, B está secuenciado después de A ), entonces la evaluación de A se completará antes de que comience la evaluación de B .
  • Si A no está secuenciado antes de B y B está secuenciado antes de A , entonces la evaluación de B se completará antes de que comience la evaluación de A .
  • Si A no está secuenciado antes de B y B no está secuenciado antes de A , entonces existen dos posibilidades:
    • Las evaluaciones de A y B están sin secuenciar  : pueden realizarse en cualquier orden y pueden superponerse (dentro de un único hilo de ejecución, el compilador puede entrelazar las instrucciones de CPU que componen A y B ).
    • Las evaluaciones de A y B están indeterminadamente secuenciadas  : pueden realizarse en cualquier orden pero no pueden superponerse: ya sea que A se complete antes de B , o B se complete antes de A . El orden puede ser opuesto la próxima vez que se evalúe la misma expresión.

Se dice que una expresión X está secuenciada antes que una expresión Y si cada cálculo de valor y cada efecto secundario asociado con X está secuenciado antes de cada cálculo de valor y cada efecto secundario asociado con la expresión Y .

Reglas

1) Cada full-expression está secuenciado antes de la siguiente full-expression.
2) Los cálculos de valor (pero no los efectos secundarios) de los operandos de cualquier operador están secuenciados antes del cálculo de valor del resultado del operador (pero no sus efectos secundarios).
3) Al llamar a una función func (independientemente de si la función es inline, y de si se utiliza sintaxis explícita de llamada a función), cada elemento de la siguiente lista se secuencia antes del siguiente elemento:
  • cada expresión de argumento y la expresión postfija que designa func
(desde C++26)
  • cada expresión o sentencia en el cuerpo de func
(desde C++26)
4) El cálculo del valor de los operadores incorporados de post-incremento y post-decremento está secuenciado antes de su efecto secundario.
5) El efecto secundario de los operadores integrados de preincremento y predecremento está secuenciado antes de su cálculo de valor (regla implícita debido a la definición como asignación compuesta).
6) El primer operando (izquierdo) del operador lógico AND incorporado && , el operador lógico OR incorporado || y el operador coma incorporado , está secuenciado antes del segundo operando (derecho).
7) El primer operando en el operador condicional ?: se evalúa antes que el segundo o tercer operando.
8) El efecto secundario (modificación del argumento izquierdo) del operador de assignment incorporado y de todos los operadores de compound assignment incorporados está secuenciado después del cálculo del valor (pero no de los efectos secundarios) de ambos argumentos izquierdo y derecho, y está secuenciado antes del cálculo del valor de la expresión de assignment (es decir, antes de devolver la referencia al objeto modificado).
9) En la inicialización de lista , cada cálculo de valor y efecto secundario de una cláusula de inicialización dada se secuencia antes de cada cálculo de valor y efecto secundario asociado con cualquier cláusula de inicialización que le siga en la lista de inicializadores separados por comas y encerrados entre llaves.
10) Una llamada a función que no está secuenciada antes ni secuenciada después de otra evaluación de expresión fuera de la función (posiblemente otra llamada a función) está indeterminadamente secuenciada con respecto a esa evaluación (el programa debe comportarse as if las instrucciones de CPU que constituyen una llamada a función no estuvieran intercaladas con instrucciones que constituyen evaluaciones de otras expresiones, incluyendo otras llamadas a función, incluso si la función fue inlineada).
La regla 10 tiene una excepción: las llamadas a función realizadas por un algoritmo de la biblioteca estándar ejecutándose bajo la política de ejecución std::execution::par_unseq no están secuenciadas y pueden estar arbitrariamente intercaladas entre sí. (since C++17)
11) La llamada a la función de asignación ( operator new ) está secuenciada de manera indeterminada con respecto a (until C++17) secuenciada antes de (since C++17) la evaluación de los argumentos del constructor en una new expresión .
12) Al retornar de una función, la inicialización por copia del temporal que es el resultado de evaluar la llamada a la función está secuenciada antes de la destrucción de todos los temporales al final del operando de la return sentencia , que, a su vez, está secuenciada antes de la destrucción de las variables locales del bloque que encierra la return sentencia.
13) En una expresión de llamada a función, la expresión que nombra la función se secuencia antes de cada expresión de argumento y cada argumento por defecto.
14) En una llamada a función, los cálculos de valor y efectos secundarios de la inicialización de cada parámetro tienen secuenciación indeterminada respecto a los cálculos de valor y efectos secundarios de cualquier otro parámetro.
15) Cada operador sobrecargado obedece a las reglas de secuenciación del operador incorporado que sobrecarga cuando se llama usando notación de operador.
16) En una expresión de subíndice E1 [ E2 ] , E1 se secuencia antes que E2 .
17) En una expresión de puntero a miembro E1. * E2 o E1 - > * E2 , E1 se secuencia antes que E2 (a menos que el tipo dinámico de E1 no contenga el miembro al que E2 hace referencia).
18) En una expresión de operador de desplazamiento E1 << E2 y E1 >> E2 , E1 se secuencia antes que E2 .
19) En cada expresión de asignación simple E1 = E2 y cada expresión de asignación compuesta E1 @ = E2 , E2 se secuencia antes que E1 .
20) Cada expresión en una lista separada por comas de expresiones en un inicializador entre paréntesis se evalúa como si fuera para una llamada a función (secuenciación indeterminada).
(desde C++17)

Comportamiento indefinido

El comportamiento es undefined en los siguientes casos:

1) Un efecto secundario en una ubicación de memoria no está secuenciado respecto a otro efecto secundario en la misma ubicación de memoria:
i = ++i + 2;       // bien definido
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, no especificado después de C++17
i = ++i + i++;     // comportamiento indefinido
2) Un efecto secundario en una ubicación de memoria no está secuenciado en relación con un cálculo de valor que utiliza el valor de cualquier objeto en la misma ubicación de memoria:
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
3) Iniciar o finalizar el lifetime de un objeto en una ubicación de memoria no está secuenciado respecto a cualquiera de las siguientes operaciones:
  • un side effect en la misma ubicación de memoria
  • un value computation utilizando el valor de cualquier objeto en la misma ubicación de memoria
  • iniciar o finalizar el lifetime de un objeto que ocupa almacenamiento que se superpone con la ubicación de memoria
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

Reglas de puntos de secuencia (hasta C++11)

Definiciones Previas a C++11

La evaluación de una expresión podría producir efectos secundarios, los cuales son: acceder a un objeto designado por un lvalue volatile, modificar un objeto, llamar a una función de E/S de la biblioteca, o llamar a una función que realice cualquiera de esas operaciones.

Un punto de secuencia es un punto en la secuencia de ejecución donde todos los efectos secundarios de las evaluaciones previas en la secuencia están completos, y ningún efecto secundario de las evaluaciones posteriores ha comenzado.

Reglas Pre-C++11

1) Existe 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 ya sea que se haya utilizado 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 los hay) que ocurre antes de la ejecución de cualquier expresión o sentencia en el cuerpo de la función.
3) Al retornar desde una función, existe un punto de secuencia después de la inicialización por copia del resultado de la llamada a la función, y antes de la destrucción de todos los objetos temporales al final de la expresión en la return sentencia (si existe).
4) Existe un punto de secuencia después de la copia del valor devuelto por una función y antes de la ejecución de cualquier expresión fuera de la función.
5) Una vez que comienza la ejecución de una función, ninguna expresión de la función llamante se evalúa hasta que se complete la ejecución de la función llamada (las funciones no pueden entrelazarse).
6) En la evaluación de cada una de las siguientes cuatro expresiones, utilizando los operadores incorporados (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

Comportamiento indefinido anterior a C++11

El comportamiento es indefinido en los siguientes casos:

1) Entre el punto de secuencia anterior y el siguiente, el valor de cualquier objeto en una ubicación de memoria se modifica más de una vez por la evaluación de una expresión:
i = ++i + i++;     // undefined behavior
i = i++ + 1;       // undefined behavior
i = ++i + 1;       // undefined behavior
++ ++i;            // undefined behavior
f(++i, ++i);       // undefined behavior
f(i = -1, i = -1); // undefined behavior
2) Entre el punto de secuencia anterior y el siguiente, para un objeto cuyo valor es modificado por la evaluación de una expresión, su valor previo es accedido de una manera diferente a determinar el valor a almacenar:
cout << i << i++; // undefined behavior
a[i] = i++;       // undefined behavior

Informes de defectos

Los siguientes informes de defectos que modifican el comportamiento se aplicaron retroactivamente a los estándares de C++ publicados anteriormente.

DR Aplicado a Comportamiento publicado Comportamiento correcto
CWG 1885 C++11 la secuenciación de la destrucción de variables
automáticas al retornar de función no era explícita
se añadieron reglas de secuenciación
CWG 1949 C++11 se utilizaba "secuenciado después" pero no estaba
definido en el estándar de C++
definido como el inverso
de "secuenciado antes"
CWG 1953 C++11 los efectos secundarios y cálculos de valor que involucran
una ubicación de memoria podrían no estar secuenciados
respecto al inicio o fin del tiempo de vida de un objeto
en la misma ubicación de memoria
el comportamiento es
indefinido en este caso
CWG 2146 C++98 los casos que involucran comportamientos indefinidos
no consideraban campos de bits
considerados

Referencias

  • Estándar C++23 (ISO/IEC 14882:2024):
  • 6.9.1 Ejecución del programa [intro.execution]
  • 7.6.1.6 Incremento y decremento [expr.post.incr]
  • 7.6.2.8 New [expr.new]
  • 7.6.14 Operador AND lógico [expr.log.and]
  • 7.6.15 Operador OR lógico [expr.log.or]
  • 7.6.16 Operador condicional [expr.cond]
  • 7.6.19 Operadores de asignación y asignación compuesta [expr.ass]
  • 7.6.20 Operador coma [expr.comma]
  • 9.4.5 Inicialización de lista [dcl.init.list]
  • Estándar C++20 (ISO/IEC 14882:2020):
  • 6.9.1 Ejecución del programa [intro.execution]
  • 7.6.1.5 Incremento y decremento [expr.post.incr]
  • 7.6.2.7 New [expr.new]
  • 7.6.14 Operador AND lógico [expr.log.and]
  • 7.6.15 Operador OR lógico [expr.log.or]
  • 7.6.16 Operador condicional [expr.cond]
  • 7.6.19 Operadores de asignación y asignación compuesta [expr.ass]
  • 7.6.20 Operador coma [expr.comma]
  • 9.4.4 Inicialización de lista [dcl.init.list]
  • Estándar C++17 (ISO/IEC 14882:2017):
  • 4.6 Ejecución del programa [intro.execution]
  • 8.2.6 Incremento y decremento [expr.post.incr]
  • 8.3.4 New [expr.new]
  • 8.14 Operador AND lógico [expr.log.and]
  • 8.15 Operador OR lógico [expr.log.or]
  • 8.16 Operador condicional [expr.cond]
  • 8.18 Operadores de asignación y asignación compuesta [expr.ass]
  • 8.19 Operador coma [expr.comma]
  • 11.6.4 Inicialización de lista [dcl.init.list]
  • Estándar C++14 (ISO/IEC 14882:2014):
  • 1.9 Ejecución del programa [intro.execution]
  • 5.2.6 Incremento y decremento [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Operador AND lógico [expr.log.and]
  • 5.15 Operador OR lógico [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]
  • Estándar C++11 (ISO/IEC 14882:2011):
  • 1.9 Ejecución del programa [intro.execution]
  • 5.2.6 Incremento y decremento [expr.post.incr]
  • 5.3.4 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]

Véase también

Documentación de C para Orden de evaluación