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

L'ordre d'évaluation de toute partie d'une expression, y compris l'ordre d'évaluation des arguments de fonction, est non spécifié (à l'exception de certains cas énumérés ci-dessous). Le compilateur peut évaluer les opérandes et autres sous-expressions dans n'importe quel ordre, et peut choisir un autre ordre lorsque la même expression est évaluée à nouveau.

Il n'existe pas de concept d'évaluation de gauche à droite ou de droite à gauche en C++. Ceci ne doit pas être confondu avec l'associativité gauche-droite et droite-gauche des opérateurs : l'expression a ( ) + b ( ) + c ( ) est analysée comme ( a ( ) + b ( ) ) + c ( ) en raison de l'associativité gauche-droite de operator + , mais c ( ) peut être évalué en premier, en dernier, ou entre a ( ) ou b ( ) à l'exécution :

#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());       // les 6 permutations de sortie sont autorisées
    return a() + b() + c(); // les 6 permutations de sortie sont autorisées
}

Sortie possible :

b
c
a
c
a 
b

Table des matières

Règles "séquencé avant" (depuis C++11)

Évaluation des Expressions

L'évaluation de chaque expression comprend :

  • Calculs de valeur : calcul de la valeur retournée par l'expression. Cela peut impliquer la détermination de l'identité de l'objet (évaluation glvalue, par exemple si l'expression retourne une référence à un objet) ou la lecture de la valeur précédemment assignée à un objet (évaluation prvalue, par exemple si l'expression retourne un nombre ou une autre valeur).
  • Initiation des effets secondaires : accès (lecture ou écriture) à un objet désigné par une glvalue volatile, modification (écriture) d'un objet, appel d'une fonction d'E/S de la bibliothèque, ou appel d'une fonction effectuant l'une de ces opérations.

Ordonnancement

Sequenced before est une relation asymétrique, transitive et binaire entre les évaluations A et B au sein du même thread.

  • Si A est séquencé avant B (ou, de manière équivalente, B est séquencé après A ), alors l'évaluation de A sera terminée avant que l'évaluation de B ne commence.
  • Si A n'est pas séquencé avant B et B est séquencé avant A , alors l'évaluation de B sera terminée avant que l'évaluation de A ne commence.
  • Si A n'est pas séquencé avant B et B n'est pas séquencé avant A , alors deux possibilités existent :
    • Les évaluations de A et B sont non séquencées  : elles peuvent être effectuées dans n'importe quel ordre et peuvent se chevaucher (au sein d'un seul thread d'exécution, le compilateur peut entrelacer les instructions CPU qui composent A et B ).
    • Les évaluations de A et B sont séquencées de manière indéterminée  : elles peuvent être effectuées dans n'importe quel ordre mais ne peuvent pas se chevaucher : soit A sera terminé avant B , soit B sera terminé avant A . L'ordre peut être opposé la prochaine fois que la même expression est évaluée.

Une expression X est dite séquencée avant une expression Y si chaque calcul de valeur et chaque effet secondaire associé à X est séquencé avant chaque calcul de valeur et chaque effet secondaire associé à l'expression Y .

Règles

1) Chaque full-expression est séquencée avant la full-expression suivante.
2) Les calculs de valeur (mais pas les effets de bord) des opérandes de tout opérateur sont séquencés avant le calcul de valeur du résultat de l'opérateur (mais pas ses effets de bord).
3) Lors de l'appel d'une fonction func (que la fonction soit inline ou non, et que la syntaxe d'appel de fonction explicite soit utilisée ou non), chaque élément de la liste suivante est séquencé avant l'élément suivant :
  • chaque expression d'argument et l'expression postfixée désignant func
(depuis C++26)
  • chaque expression ou instruction dans le corps de func
(depuis C++26)
4) Le calcul de la valeur des opérateurs intégrés de post-incrémentation et post-décrémentation est séquencé avant son effet secondaire.
5) L'effet secondaire des opérateurs intégrés de pré-incrémentation et pré-décrémentation est séquencé avant le calcul de sa valeur (règle implicite due à la définition en tant qu'affectation composée).
6) Le premier opérande (gauche) de l'opérateur logique ET intégré && , de l'opérateur logique OU intégré || et de l' opérateur virgule intégré , est séquencé avant le second opérande (droit).
7) Le premier opérande dans l' opérateur conditionnel ?: est séquencé avant le deuxième ou le troisième opérande.
8) L'effet secondaire (modification de l'argument gauche) de l'opérateur d'assignation assignment intégré et de tous les opérateurs d'assignation compound intégrés est séquencé après le calcul de la valeur (mais pas les effets secondaires) des arguments gauche et droit, et est séquencé avant le calcul de la valeur de l'expression d'assignation (c'est-à-dire avant de retourner la référence à l'objet modifié).
9) Dans l'initialisation par liste , chaque calcul de valeur et effet secondaire d'une clause d'initialisation donnée est séquencé avant chaque calcul de valeur et effet secondaire associé à toute clause d'initialisation qui la suit dans la liste d'initialisateurs entre accolades séparés par des virgules.
10) Un appel de fonction qui n'est pas séquencé avant ou séquencé après une autre évaluation d'expression en dehors de la fonction (éventuellement un autre appel de fonction) est séquencé de manière indéterminée par rapport à cette évaluation (le programme doit se comporter as if les instructions CPU qui constituent un appel de fonction n'étaient pas entrelacées avec les instructions constituant les évaluations d'autres expressions, y compris d'autres appels de fonction, même si la fonction était inline).
La règle 10 a une exception : les appels de fonction effectués par un algorithme de la bibliothèque standard s'exécutant sous la politique d'exécution std::execution::par_unseq ne sont pas séquencés et peuvent être entrelacés arbitrairement les uns avec les autres. (since C++17)
11) L'appel à la fonction d'allocation ( operator new ) est séquencé de manière indéterminée par rapport à (jusqu'en C++17) séquencé avant (depuis C++17) l'évaluation des arguments du constructeur dans une new expression .
12) Lors du retour d'une fonction, l'initialisation par copie du temporaire qui est le résultat de l'évaluation de l'appel de fonction est séquencée avant la destruction de tous les temporaires à la fin de l'opérande de l' return statement , qui, à son tour, est séquencée avant la destruction des variables locales du bloc englobant l'instruction return .
13) Dans une expression d'appel de fonction, l'expression qui nomme la fonction est séquencée avant chaque expression d'argument et chaque argument par défaut.
14) Dans un appel de fonction, les calculs de valeur et les effets secondaires de l'initialisation de chaque paramètre sont séquencés de manière indéterminée par rapport aux calculs de valeur et effets secondaires de tout autre paramètre.
15) Chaque opérateur surchargé respecte les règles de séquencement de l'opérateur intégré qu'il surcharge lorsqu'il est appelé en utilisant la notation d'opérateur.
16) Dans une expression d'indice E1 [ E2 ] , E1 est séquencé avant E2 .
17) Dans une expression pointeur-vers-membre E1. * E2 ou E1 - > * E2 , E1 est séquencé avant E2 (sauf si le type dynamique de E1 ne contient pas le membre auquel E2 se réfère).
18) Dans une expression d'opérateur de décalage E1 << E2 et E1 >> E2 , E1 est séquencé avant E2 .
19) Dans chaque expression d'affectation simple E1 = E2 et chaque expression d'affectation composée E1 @ = E2 , E2 est séquencé avant E1 .
20) Chaque expression dans une liste d'expressions séparées par des virgules dans un initialiseur entre parenthèses est évaluée comme pour un appel de fonction (séquencement indéterminé).
(depuis C++17)

Comportement indéfini

Le comportement est indéfini dans les cas suivants :

1) Un effet de bord sur un emplacement mémoire n'est pas séquencé par rapport à un autre effet de bord sur le même emplacement mémoire :
i = ++i + 2;       // bien défini
i = i++ + 2;       // comportement indéfini jusqu'à C++17
f(i = -2, i = -2); // comportement indéfini jusqu'à C++17
f(++i, ++i);       // comportement indéfini jusqu'à C++17, non spécifié après C++17
i = ++i + i++;     // comportement indéfini
2) Un effet de bord sur un emplacement mémoire n'est pas séquencé par rapport à un calcul de valeur utilisant la valeur de tout objet dans le même emplacement mémoire :
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
3) Le début ou la fin de la durée de vie d'un objet dans un emplacement mémoire n'est pas séquencé par rapport à l'une des opérations suivantes :
  • un effet de bord sur le même emplacement mémoire
  • un calcul de valeur utilisant la valeur de tout objet dans le même emplacement mémoire
  • le début ou la fin de la durée de vie d'un objet occupant un stockage qui chevauche l'emplacement mémoire
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

Règles des points de séquence (jusqu'à C++11)

Définitions pré-C++11

L'évaluation d'une expression peut produire des effets secondaires, qui sont : accéder à un objet désigné par une lvalue volatile, modifier un objet, appeler une fonction d'E/S de bibliothèque, ou appeler une fonction qui effectue l'une de ces opérations.

Un point de séquence est un point dans la séquence d'exécution où tous les effets secondaires des évaluations précédentes dans la séquence sont terminés, et aucun effet secondaire des évaluations suivantes n'a commencé.

Règles pré-C++11

1) Il y a un point de séquence à la fin de chaque expression complète (généralement, au niveau du point-virgule).
2) Lors de l'appel d'une fonction (qu'elle soit inline ou non et que la syntaxe d'appel de fonction ait été utilisée ou non), il y a un point de séquence après l'évaluation de tous les arguments de la fonction (le cas échéant) qui se produit avant l'exécution de toute expression ou instruction dans le corps de la fonction.
3) Lors du retour d'une fonction, il y a un point de séquence après l'initialisation par copie du résultat de l'appel de fonction, et avant la destruction de tous les objets temporaires à la fin de l' expression dans l' return statement (le cas échéant).
4) Il y a un point de séquence après la copie de la valeur de retour d'une fonction et avant l'exécution de toute expression en dehors de la fonction.
5) Une fois que l'exécution d'une fonction commence, aucune expression de la fonction appelante n'est évaluée jusqu'à ce que l'exécution de la fonction appelée soit terminée (les fonctions ne peuvent pas être entrelacées).
6) Dans l'évaluation de chacune des quatre expressions suivantes, en utilisant les opérateurs intégrés (non surchargés), il y a un point de séquence après l'évaluation de l'expression a .
a && b
a || b
a ? b : c
a , b

Comportement indéfini avant C++11

Le comportement est indéfini dans les cas suivants :

1) Entre le point de séquence précédent et le suivant, la valeur de tout objet dans un emplacement mémoire est modifiée plus d'une fois par l'évaluation d'une expression :
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 le point de séquence précédent et le suivant, pour un objet dont la valeur est modifiée par l'évaluation d'une expression, sa valeur antérieure est accédée d'une manière autre que pour déterminer la valeur à stocker :
cout << i << i++; // undefined behavior
a[i] = i++;       // undefined behavior

Rapports de défauts

Les rapports de défauts modifiant le comportement suivants ont été appliqués rétroactivement aux normes C++ précédemment publiées.

DR Appliqué à Comportement publié Comportement corrigé
CWG 1885 C++11 l'ordonnancement de la destruction des variables
automatiques au retour de fonction n'était pas explicite
règles d'ordonnancement ajoutées
CWG 1949 C++11 « séquencé après » était utilisé mais non défini dans le standard C++ défini comme l'inverse
de « séquencé avant »
CWG 1953 C++11 les effets de bord et calculs de valeur impliquant un emplacement
mémoire pouvaient être non séquencés par rapport au début ou à la fin
de la durée de vie d'un objet dans le même emplacement mémoire
le comportement est
indéfini dans ce cas
CWG 2146 C++98 les cas impliquant des comportements indéfinis ne prenaient pas en compte les champs de bits pris en compte

Références

  • Norme C++23 (ISO/CEI 14882:2024) :
  • 6.9.1 Exécution du programme [intro.execution]
  • 7.6.1.6 Incrément et décrément [expr.post.incr]
  • 7.6.2.8 New [expr.new]
  • 7.6.14 Opérateur ET logique [expr.log.and]
  • 7.6.15 Opérateur OU logique [expr.log.or]
  • 7.6.16 Opérateur conditionnel [expr.cond]
  • 7.6.19 Opérateurs d'affectation et d'affectation composée [expr.ass]
  • 7.6.20 Opérateur virgule [expr.comma]
  • 9.4.5 Initialisation de liste [dcl.init.list]
  • Norme C++20 (ISO/CEI 14882:2020) :
  • 6.9.1 Exécution du programme [intro.execution]
  • 7.6.1.5 Incrémentation et décrémentation [expr.post.incr]
  • 7.6.2.7 Opérateur new [expr.new]
  • 7.6.14 Opérateur ET logique [expr.log.and]
  • 7.6.15 Opérateur OU logique [expr.log.or]
  • 7.6.16 Opérateur conditionnel [expr.cond]
  • 7.6.19 Opérateurs d'affectation et d'affectation composée [expr.ass]
  • 7.6.20 Opérateur virgule [expr.comma]
  • 9.4.4 Initialisation par liste [dcl.init.list]
  • Norme C++17 (ISO/IEC 14882:2017) :
  • 4.6 Exécution du programme [intro.execution]
  • 8.2.6 Incrémentation et décrémentation [expr.post.incr]
  • 8.3.4 Opérateur new [expr.new]
  • 8.14 Opérateur logique ET [expr.log.and]
  • 8.15 Opérateur logique OU [expr.log.or]
  • 8.16 Opérateur conditionnel [expr.cond]
  • 8.18 Opérateurs d'affectation et d'affectation composée [expr.ass]
  • 8.19 Opérateur virgule [expr.comma]
  • 11.6.4 Initialisation par liste [dcl.init.list]
  • Norme C++14 (ISO/CEI 14882:2014) :
  • 1.9 Exécution du programme [intro.execution]
  • 5.2.6 Incrément et décrément [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Opérateur ET logique [expr.log.and]
  • 5.15 Opérateur OU logique [expr.log.or]
  • 5.16 Opérateur conditionnel [expr.cond]
  • 5.17 Opérateurs d'affectation et d'affectation composée [expr.ass]
  • 5.18 Opérateur virgule [expr.comma]
  • 8.5.4 Initialisation de liste [dcl.init.list]
  • Norme C++11 (ISO/CEI 14882:2011) :
  • 1.9 Exécution du programme [intro.execution]
  • 5.2.6 Incrément et décrément [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Opérateur logique ET [expr.log.and]
  • 5.15 Opérateur logique OU [expr.log.or]
  • 5.16 Opérateur conditionnel [expr.cond]
  • 5.17 Opérateurs d'affectation et d'affectation composée [expr.ass]
  • 5.18 Opérateur virgule [expr.comma]
  • 8.5.4 Initialisation de liste [dcl.init.list]

Voir aussi

  • Priorité des opérateurs qui définit la manière dont les expressions sont construites à partir de leur représentation en code source.
Documentation C pour Ordre d'évaluation