Espacios de nombres
Variantes
Acciones

Orden de evaluación

De cppreference.com
< cpp‎ | language
 
 
 
 

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

1) Cada cálculo de valor y efecto secundario de una expresión completa que sea:
(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.),
incluyendo las conversiones implícitas aplicadas al resultado de la expresión, llamadas a destructor para temporales, inicializadores de miembro por defecto (al inicializar agregados), y cualquier otro concepto del lenguaje que implique una llamada a función, es secuenciado antes de cada cálculo de valor y efectos secundarios de la siguiente expresión completa.
2) Los cálculos de valor (pero no los efectos secundarios) de los operandos de cualquier operador son secuenciados antes que el cálculo del valor del resultado del operador (pero no sus efectos secundarios).
3) Cuando se llama a una función (sea o no sea inline, y se use o no la sintaxis explícita de llamada a función), cada cálculo de valor y los efectos secundarios asociados con cualquier expresión de argumento, o con la expresión posfija que designa la función llamada, es secuenciada antes que la ejecución de toda expresión o instrucción en el cuerpo la función llamada.
4) El cálculo del valor del operador integrado posincremento y posdecremento es secuenciado antes que sus efectos secundarios.
5) El efecto secundario del operador integrado preincremento y predecremento es secuenciado antes que el cálculo de su valor (regla implícita debido a la definición como asignación compuesta).
6) Cada cálculo de valor y efecto secundario del primer argumento (a la izquierda) del operador integrado AND lógico && y del operador integrado OR lógico || es secuenciado antes que cualquier cálculo y efectos secundarios del segundo argumento (a la derecha).
7) Cada cálculo de valor y efecto secundario asociado con la primera expresión en el operador condicional ?: es secuenciado antes que cualquier cálculo de valor y efecto secundario asociado con la segunda y tercera expresión.
8) El efecto secundario (modificación del argumento izquierdo) del operador integrado de asignación y todos los operadores integrados de asignación compuesta es secuenciado después que el cálculo del valor (pero no de los efectos secundarios) de los argumentos izquierdo y derecho, y es secuenciado antes que el cálculo del valor de la expresión de asignación (es decir, antes de devolver la referencia al objeto modificado)
9) Cada cálculo de valor y efecto secundario del primer argumento (izquierda) del operador coma integrado , es secuenciado antes que cualquier cálculo de valor y efecto secundario del segundo argumento (derecha).
10) En inicialización de lista, cada cálculo de valor y efecto secundario de una cláusula de inicialización dada es secuenciada antes que todo 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 entre corchetes.
11) Una llamada a función que no es secuenciada antes o secuenciada después que otra llamada a función es secuenciada indeterminadamente (el programa debe comportarse como si las instrucciones de la CPU que constituyen llamadas a diferentes funciones no se entrelazaron, incluso si las funciones eran inline).
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)
12) La llamada a la función de asignación (operador new) es secuenciada indeterminadamente respecto a (hasta C++17) secuenciada antes que (desde C++17) la evaluación de los argumentos del constructor en una expresión new
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 E2
20) 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

Documentación de C para Orden de evaluación