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

Die Reihenfolge der Auswertung jedes Teils eines Ausdrucks, einschließlich der Reihenfolge der Auswertung von Funktionsargumenten, ist unspezifiziert (mit einigen unten aufgeführten Ausnahmen). Der Compiler kann Operanden und andere Teilausdrücke in beliebiger Reihenfolge auswerten und kann eine andere Reihenfolge wählen, wenn derselbe Ausdruck erneut ausgewertet wird.

Es gibt kein Konzept von Links-nach-rechts oder Rechts-nach-links Auswertung in C++. Dies ist nicht zu verwechseln mit Links-nach-rechts und Rechts-nach-links Assoziativität von Operatoren: der Ausdruck a ( ) + b ( ) + c ( ) wird geparst als ( a ( ) + b ( ) ) + c ( ) aufgrund der Links-nach-rechts Assoziativität von operator + , aber c ( ) könnte zuerst, zuletzt oder zwischen a ( ) oder b ( ) zur Laufzeit ausgewertet werden:

#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());       // alle 6 Permutationen der Ausgabe sind erlaubt
    return a() + b() + c(); // alle 6 Permutationen der Ausgabe sind erlaubt
}

Mögliche Ausgabe:

b
c
a
c
a 
b

Inhaltsverzeichnis

"Sequenced before"-Regeln (seit C++11)

Auswertung von Ausdrücken

Die Auswertung jedes Ausdrucks umfasst:

  • Wertberechnungen : Berechnung des Werts, der durch den Ausdruck zurückgegeben wird. Dies kann die Bestimmung der Identität des Objekts (Glvalue-Auswertung, z.B. wenn der Ausdruck eine Referenz auf ein Objekt zurückgibt) oder das Lesen eines zuvor einem Objekt zugewiesenen Werts (Prvalue-Auswertung, z.B. wenn der Ausdruck eine Zahl oder einen anderen Wert zurückgibt) umfassen.
  • Einleitung von Nebeneffekten : Zugriff (Lesen oder Schreiben) auf ein durch einen volatile-Glvalue bezeichnetes Objekt, Modifikation (Schreiben) eines Objekts, Aufruf einer Bibliotheks-E/A-Funktion oder Aufruf einer Funktion, die eine dieser Operationen durchführt.

Reihenfolge

Sequenced before ist eine asymmetrische, transitive, paarweise Beziehung zwischen Auswertungen A und B innerhalb desselben Threads.

  • Wenn A vor B sequenziert ist (oder äquivalent, B nach A sequenziert ist), dann wird die Auswertung von A abgeschlossen sein, bevor die Auswertung von B beginnt.
  • Wenn A nicht vor B sequenziert ist und B vor A sequenziert ist, dann wird die Auswertung von B abgeschlossen sein, bevor die Auswertung von A beginnt.
  • Wenn A nicht vor B sequenziert ist und B nicht vor A sequenziert ist, dann existieren zwei Möglichkeiten:
    • Die Auswertungen von A und B sind nicht sequenziert  : sie können in beliebiger Reihenfolge ausgeführt werden und können sich überlappen (innerhalb eines einzelnen Ausführungsthreads kann der Compiler die CPU-Befehle, die A und B umfassen, verschachteln).
    • Die Auswertungen von A und B sind indeterminiert sequenziert  : sie können in beliebiger Reihenfolge ausgeführt werden, dürfen sich aber nicht überlappen: entweder wird A vor B abgeschlossen, oder B wird vor A abgeschlossen. Die Reihenfolge kann beim nächsten Auswerten des gleichen Ausdrucks umgekehrt sein.

Ein Ausdruck X wird als vorangestellt zu einem Ausdruck Y bezeichnet, wenn jede Wertberechnung und jeder Nebeneffekt von X vor jeder Wertberechnung und jedem Nebeneffekt des Ausdrucks Y ausgeführt wird.

Regeln

1) Jeder full-expression ist vor dem nächsten full-expression sequenziert.
2) Die Wertberechnungen (aber nicht die Nebeneffekte) der Operanden jedes Operators sind vor der Wertberechnung des Ergebnisses des Operators (aber nicht dessen Nebeneffekten) sequenziert.
3) Beim Aufruf einer Funktion func (unabhängig davon, ob die Funktion inline ist und ob explizite Funktionsaufrufsyntax verwendet wird), wird jedes Element in der folgenden Liste vor dem nächsten Element sequenziert:
  • jeder Argumentausdruck und der Postfix-Ausdruck, der func bezeichnet
(seit C++26)
  • jeder Ausdruck oder jede Anweisung im Körper von func
(seit C++26)
4) Die Wertberechnung der eingebauten Postinkrement- und Postdekrement-Operatoren ist vor deren Nebeneffekt sequenziert.
5) Der Nebeneffekt der eingebauten Präinkrement- und Prädekrement-Operatoren ist vor deren Wertberechnung sequenziert (implizite Regel aufgrund der Definition als zusammengesetzte Zuweisung).
6) Der erste (linke) Operand des eingebauten logischen UND-Operators && , des eingebauten logischen ODER-Operators || und des eingebauten Komma-Operators , wird vor dem zweiten (rechten) Operanden sequenziert.
7) Der erste Operand im Conditional Operator ?: wird vor dem zweiten oder dritten Operanden sequenziert.
8) Der Nebeneffekt (Modifikation des linken Arguments) des eingebauten assignment Operators und aller eingebauten compound assignment Operatoren ist nach der Wertberechnung (aber nicht den Nebeneffekten) sowohl des linken als auch des rechten Arguments sequenziert und vor der Wertberechnung des Zuweisungsausdrucks sequenziert (das heißt, bevor die Referenz auf das modifizierte Objekt zurückgegeben wird).
9) Bei der Listeninitialisierung wird jede Wertberechnung und jeder Seiteneffekt eines gegebenen Initialisierer-Ausdrucks vor jeder Wertberechnung und jedem Seiteneffekt ausgeführt, die mit einem beliebigen nachfolgenden Initialisierer-Ausdruck in der geschweiften, kommagetrennten Initialisiererliste assoziiert sind.
10) Ein Funktionsaufruf, der nicht vor oder nach einer anderen Ausdrucksauswertung außerhalb der Funktion (möglicherweise einem anderen Funktionsaufruf) sequenziert ist, ist in Bezug auf diese Auswertung unbestimmt sequenziert (das Programm muss sich so verhalten, als ob die CPU-Befehle, die einen Funktionsaufruf bilden, nicht mit Befehlen verschachtelt wären, die Auswertungen anderer Ausdrücke bilden, einschließlich anderer Funktionsaufrufe, selbst wenn die Funktion inline expandiert wurde).
Die Regel 10 hat eine Ausnahme: Funktionsaufrufe, die von einem Standardbibliotheksalgorithmus unter der std::execution::par_unseq Ausführungsrichtlinie ausgeführt werden, sind nicht sequenziert und können beliebig miteinander verschachtelt werden. (since C++17)
11) Der Aufruf der Allokierungsfunktion ( operator new ) ist in unbestimmter Reihenfolge mit (bis C++17) vor (seit C++17) der Auswertung der Konstruktorargumente in einem new -Ausdruck angeordnet.
12) Bei der Rückkehr aus einer Funktion ist die Kopierinitialisierung des Temporärs, das das Ergebnis der Auswertung des Funktionsaufrufs ist, vor der Zerstörung aller Temporärs am Ende des Operanden der return -Anweisung sequenziert, was wiederum vor der Zerstörung der lokalen Variablen des Blocks, der die return -Anweisung umschließt, sequenziert ist.
13) In einem Funktionsaufrufausdruck wird der Ausdruck, der die Funktion benennt, vor jedem Argumentausdruck und jedem Standardargument sequenziert.
14) In einem Funktionsaufruf sind Wertberechnungen und Seiteneffekte der Initialisierung jedes Parameters in unbestimmter Reihenfolge bezüglich Wertberechnungen und Seiteneffekten jedes anderen Parameters.
15) Jeder überladene Operator befolgt die Sequenzierungsregeln des eingebauten Operators, den er überlädt, wenn er mit Operator-Notation aufgerufen wird.
16) In einem Indexausdruck E1 [ E2 ] wird E1 vor E2 sequenziert.
17) In einem Zeiger-auf-Mitglied-Ausdruck E1. * E2 oder E1 - > * E2 wird E1 vor E2 sequenziert (sofern der dynamische Typ von E1 nicht das Mitglied enthält, auf das E2 verweist).
18) In einem Schiebeoperatorausdruck E1 << E2 und E1 >> E2 wird E1 vor E2 sequenziert.
19) In jedem einfachen Zuweisungsausdruck E1 = E2 und jedem zusammengesetzten Zuweisungsausdruck E1 @ = E2 wird E2 vor E1 sequenziert.
20) Jeder Ausdruck in einer durch Kommas getrennten Liste von Ausdrücken in einem geklammerten Initialisierer wird wie für einen Funktionsaufruf ausgewertet (in unbestimmter Reihenfolge).
(seit C++17)

Undefiniertes Verhalten

Das Verhalten ist undefined in den folgenden Fällen:

1) Ein Nebeneffekt auf einen Speicherbereich ist ungeordnet in Bezug auf einen anderen Nebeneffekt auf denselben Speicherbereich:
i = ++i + 2;       // well-defined
i = i++ + 2;       // undefined behavior until C++17
f(i = -2, i = -2); // undefined behavior until C++17
f(++i, ++i);       // undefined behavior until C++17, unspecified after C++17
i = ++i + i++;     // undefined behavior
2) Ein Nebeneffekt auf einen Speicherbereich ist unsequenziert in Bezug auf eine Werteberechnung, die den Wert eines beliebigen Objekts im selben Speicherbereich verwendet:
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
3) Das Beginnen oder Beenden der Lebensdauer eines Objekts an einer Speicherstelle ist unsequenziert in Bezug auf jede der folgenden Operationen:
  • einen Nebeneffekt auf derselben Speicherstelle
  • eine Werteberechnung unter Verwendung des Werts eines beliebigen Objekts in derselben Speicherstelle
  • das Beginnen oder Beenden der Lebensdauer eines Objekts, das Speicher belegt, der sich mit der Speicherstelle überschneidet
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

Sequenzpunkt-Regeln (bis C++11)

Pre-C++11 Definitionen

Die Auswertung eines Ausdrucks kann Nebeneffekte erzeugen, welche sind: Zugriff auf ein Objekt, das durch einen volatile L-Wert bezeichnet wird, Modifizieren eines Objekts, Aufrufen einer Bibliotheks-I/O-Funktion oder Aufrufen einer Funktion, die eine dieser Operationen durchführt.

Ein sequence point ist ein Punkt in der Ausführungssequenz, an dem alle Nebeneffekte der vorherigen Auswertungen in der Sequenz abgeschlossen sind und keine Nebeneffekte der nachfolgenden Auswertungen begonnen haben.

Pre-C++11 Regeln

1) Es gibt einen Sequenzpunkt am Ende jedes full-expression (typischerweise am Semikolon).
2) Beim Aufruf einer Funktion (unabhängig davon, ob die Funktion inline ist und ob die Funktionsaufrufsyntax verwendet wurde) gibt es einen Sequenzpunkt nach der Auswertung aller Funktionsargumente (falls vorhanden), der vor der Ausführung von Ausdrücken oder Anweisungen im Funktionskörper stattfindet.
3) Bei der Rückkehr aus einer Funktion gibt es einen Sequenzpunkt nach der Kopierinitialisierung des Ergebnisses des Funktionsaufrufs und vor der Zerstörung aller temporären Objekte am Ende des expression in der return Anweisung (falls vorhanden).
4) Es gibt einen Sequenzpunkt nach dem Kopieren eines zurückgegebenen Funktionswerts und vor der Ausführung von Ausdrücken außerhalb der Funktion.
5) Sobald die Ausführung einer Funktion beginnt, werden keine Ausdrücke aus der aufrufenden Funktion ausgewertet, bis die Ausführung der aufgerufenen Funktion abgeschlossen ist (Funktionen können nicht verschachtelt ausgeführt werden).
6) Bei der Auswertung jedes der folgenden vier Ausdrücke, unter Verwendung der eingebauten (nicht überladenen) Operatoren, gibt es einen Sequenzpunkt nach der Auswertung des Ausdrucks a .
a && b
a || b
a ? b : c
a , b

Pre-C++11 Undefiniertes Verhalten

Das Verhalten ist undefined in den folgenden Fällen:

1) Zwischen dem vorherigen und nächsten Sequenzpunkt wird der Wert eines Objekts an einem Speicherort mehr als einmal durch die Auswertung eines Ausdrucks geändert:
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) Zwischen dem vorherigen und dem nächsten Sequenzpunkt wird für ein Objekt, dessen Wert durch die Auswertung eines Ausdrucks geändert wird, sein vorheriger Wert auf eine andere Weise als zur Bestimmung des zu speichernden Werts zugegriffen:
cout << i << i++; // undefined behavior
a[i] = i++;       // undefined behavior

Fehlerberichte

Die folgenden verhaltensändernden Fehlerberichte wurden rückwirkend auf zuvor veröffentlichte C++-Standards angewendet.

DR Angewendet auf Verhalten wie veröffentlicht Korrigiertes Verhalten
CWG 1885 C++11 Reihenfolge der Zerstörung automatischer
Variablen bei Funktionsrückgabe war nicht explizit
Reihenfolgeregeln hinzugefügt
CWG 1949 C++11 "sequenced after" wurde verwendet, aber nicht im C++-Standard definiert definiert als das Inverse
von "sequenced before"
CWG 1953 C++11 Seiteneffekte und Wertberechnungen bezüglich eines Speicher-
orts konnten in Bezug auf Beginn oder Ende der Lebensdauer
eines Objekts am selben Speicherort ungeordnet sein
das Verhalten ist
in diesem Fall undefiniert
CWG 2146 C++98 die Fälle mit undefiniertem Verhalten berücksichtigten keine Bitfelder berücksichtigt

Referenzen

  • C++23-Standard (ISO/IEC 14882:2024):
  • 6.9.1 Programmausführung [intro.execution]
  • 7.6.1.6 Inkrement und Dekrement [expr.post.incr]
  • 7.6.2.8 New [expr.new]
  • 7.6.14 Logischer UND-Operator [expr.log.and]
  • 7.6.15 Logischer ODER-Operator [expr.log.or]
  • 7.6.16 Bedingungsoperator [expr.cond]
  • 7.6.19 Zuweisungs- und zusammengesetzte Zuweisungsoperatoren [expr.ass]
  • 7.6.20 Kommaoperator [expr.comma]
  • 9.4.5 Listeninitialisierung [dcl.init.list]
  • C++20-Standard (ISO/IEC 14882:2020):
  • 6.9.1 Programmausführung [intro.execution]
  • 7.6.1.5 Inkrement und Dekrement [expr.post.incr]
  • 7.6.2.7 New [expr.new]
  • 7.6.14 Logischer UND-Operator [expr.log.and]
  • 7.6.15 Logischer ODER-Operator [expr.log.or]
  • 7.6.16 Bedingungsoperator [expr.cond]
  • 7.6.19 Zuweisungs- und zusammengesetzte Zuweisungsoperatoren [expr.ass]
  • 7.6.20 Kommaoperator [expr.comma]
  • 9.4.4 Listeninitialisierung [dcl.init.list]
  • C++17-Standard (ISO/IEC 14882:2017):
  • 4.6 Programmausführung [intro.execution]
  • 8.2.6 Inkrement und Dekrement [expr.post.incr]
  • 8.3.4 New [expr.new]
  • 8.14 Logischer UND-Operator [expr.log.and]
  • 8.15 Logischer ODER-Operator [expr.log.or]
  • 8.16 Bedingungsoperator [expr.cond]
  • 8.18 Zuweisungs- und zusammengesetzte Zuweisungsoperatoren [expr.ass]
  • 8.19 Kommaoperator [expr.comma]
  • 11.6.4 Listeninitialisierung [dcl.init.list]
  • C++14-Standard (ISO/IEC 14882:2014):
  • 1.9 Programmausführung [intro.execution]
  • 5.2.6 Inkrement und Dekrement [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Logischer UND-Operator [expr.log.and]
  • 5.15 Logischer ODER-Operator [expr.log.or]
  • 5.16 Bedingungsoperator [expr.cond]
  • 5.17 Zuweisungs- und zusammengesetzte Zuweisungsoperatoren [expr.ass]
  • 5.18 Kommaoperator [expr.comma]
  • 8.5.4 Listeninitialisierung [dcl.init.list]
  • C++11-Standard (ISO/IEC 14882:2011):
  • 1.9 Programmausführung [intro.execution]
  • 5.2.6 Inkrement und Dekrement [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Logischer UND-Operator [expr.log.and]
  • 5.15 Logischer ODER-Operator [expr.log.or]
  • 5.16 Bedingungsoperator [expr.cond]
  • 5.17 Zuweisungs- und zusammengesetzte Zuweisungsoperatoren [expr.ass]
  • 5.18 Komma-Operator [expr.comma]
  • 8.5.4 Listeninitialisierung [dcl.init.list]

Siehe auch

C-Dokumentation für Auswertungsreihenfolge