Namensräume
Varianten
Aktionen

Reihenfolge der Auswertung

Von cppreference.com
< cpp‎ | Sprache
 
 
C++ Sprache
Allgemeine Themen
Kontrollfluss
Bedingte Ausführungsaussagen
if
Iterationsanweisungen (Schleifen)
for
Bereichs-for (C++11)
Sprunganweisungen
Funktionen
Funktionsdeklaration
Lambda-Funktionsausdruck
inline-Spezifizierer
Dynamische Ausnahmespezifikationen (bis C++17*)
noexcept-Spezifizierer (C++11)
Ausnahmen
Namensräume
Typen
Spezifizierer
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Speicherdauer-Spezifizierer
Initialisierung
Ausdrücke
Wertkategorien
Reihenfolge der Auswertung
Alternative Darstellungen
Literale
Boolesch - Ganzzahl - Gleitkommazahl
Zeichen - String - nullptr (C++11)
Benutzerdefinierte (C++11)
Dienstprogramme
Attribute (C++11)
Typen
typedef-Deklaration
Typalias-Deklaration (C++11)
Umwandlungen
Speicherzuweisung
Klassen
Klassenspezifische Funktionseigenschaften
explicit (C++11)
static

Spezielle Member-Funktionen
Templates
Sonstiges
 
 

Die Reihenfolge der Auswertung jeglicher Teile eines beliebigen Ausdrucks, einschließlich der Reihenfolge der Auswertung von Funktionsargumenten, ist nicht spezifiziert (mit einigen Ausnahmen, die unten aufgeführt sind). Der Compiler kann Operanden und andere Unterausdrücke in beliebiger Reihenfolge auswerten und kann eine andere Reihenfolge wählen, wenn derselbe Ausdruck erneut ausgewertet wird.

In C++ gibt es kein Konzept der Links-nach-rechts- oder Rechts-nach-links-Auswertung. Dies ist nicht mit der Links-nach-rechts- und Rechts-nach-links-Assoziativität von Operatoren zu verwechseln: Der Ausdruck a() + b() + c() wird aufgrund der Links-nach-rechts-Assoziativität von operator+ als (a() + b()) + c() geparst, aber c() kann zur Laufzeit zuerst, zuletzt oder zwischen a() oder b() 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());       // all 6 permutations of output are allowed
    return a() + b() + c(); // all 6 permutations of output are allowed
}

Mögliche Ausgabe

b
c
a
c
a 
b

Inhalt

[bearbeiten] "Sequenced before"-Regeln (seit C++11)

[bearbeiten] Auswertung von Ausdrücken

Die Auswertung jedes Ausdrucks beinhaltet

  • Wertberechnungen: Berechnung des Werts, der vom Ausdruck zurückgegeben wird. Dies kann die Ermittlung der Identität des Objekts (glvalue-Auswertung, z. B. wenn der Ausdruck eine Referenz auf ein Objekt zurückgibt) oder das Lesen des zuvor einem Objekt zugewiesenen Werts beinhalten (prvalue-Auswertung, z. B. wenn der Ausdruck eine Zahl oder einen anderen Wert zurückgibt).
  • Einleitung von Seiteneffekten: Zugriff (Lesen oder Schreiben) auf ein Objekt, das durch einen volatile-lvalue bezeichnet wird, Modifikation (Schreiben) eines Objekts, Aufruf einer Bibliotheks-I/O-Funktion oder Aufruf einer Funktion, die eine dieser Operationen ausführt.

[bearbeiten] Reihenfolge

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

  • Wenn A vor B ausgewertet wird (oder, gleichbedeutend, B nach A ausgewertet wird), dann ist die Auswertung von A abgeschlossen, bevor die Auswertung von B beginnt.
  • Wenn A nicht vor B ausgewertet wird und B vor A ausgewertet wird, dann ist die Auswertung von B abgeschlossen, bevor die Auswertung von A beginnt.
  • Wenn A nicht vor B ausgewertet wird und B nicht vor A ausgewertet wird, dann existieren zwei Möglichkeiten:
    • Auswertungen von A und B sind unsequenced : Sie können in beliebiger Reihenfolge durchgeführt werden und sich überlappen (innerhalb eines einzelnen Ausführungs-Threads kann der Compiler die CPU-Instruktionen, aus denen A und B bestehen, verschachteln).
    • Auswertungen von A und B sind indeterminately sequenced : Sie können in beliebiger Reihenfolge durchgeführt werden, dürfen sich aber nicht überlappen: Entweder ist A vor B abgeschlossen, oder B ist vor A abgeschlossen. Die Reihenfolge kann beim nächsten Mal, wenn derselbe Ausdruck ausgewertet wird, umgekehrt sein.

Ein Ausdruck X wird als sequenced before (sequenziert vor) einem Ausdruck Y bezeichnet, wenn jede Wertberechnung und jeder Seiteneffekt, der mit X verbunden ist, vor jeder Wertberechnung und jedem Seiteneffekt, der mit dem Ausdruck Y verbunden ist, sequenziert wird.

[bearbeiten] Regeln

1) Jeder vollständige Ausdruck wird vor dem nächsten vollständigen Ausdruck sequenziert.
2) Die Wertberechnungen (aber nicht die Seiteneffekte) der Operanden eines beliebigen Operators werden vor der Wertberechnung des Ergebnisses des Operators (aber nicht dessen Seiteneffekten) sequenziert.
3) Beim Aufruf einer Funktion func (unabhängig davon, ob die Funktion inline ist oder ob explizite Funktionsaufrufsyntax verwendet wird), wird jeder Punkt in der folgenden Liste vor dem nächsten Punkt 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 integrierten Post-Inkrement- und Post-Dekrement-Operatoren wird vor deren Seiteneffekt sequenziert.
5) Der Seiteneffekt der integrierten Prä-Inkrement- und Prä-Dekrement-Operatoren wird vor deren Wertberechnung sequenziert (implizite Regel aufgrund der Definition als zusammengesetzte Zuweisung).
6) Der erste (linke) Operand des integrierten logischen AND-Operators &&, des integrierten logischen OR-Operators || und des integrierten Komma-Operators , wird vor dem zweiten (rechten) Operanden sequenziert.
7) Der erste Operand im bedingten Operator ?: wird vor dem zweiten oder dritten Operanden sequenziert.
8) Der Seiteneffekt (Modifikation des linken Arguments) des integrierten Zuweisungsoperators und aller integrierten zusammengesetzten Zuweisungsoperatoren erfolgt nach der Wertberechnung (aber nicht den Seiteneffekten) sowohl des linken als auch des rechten Arguments und erfolgt vor der Wertberechnung des Zuweisungsausdrucks (d. h. vor der Rückgabe der Referenz auf das modifizierte Objekt).
9) Bei der List-Initialisierung wird jede Wertberechnung und jeder Seiteneffekt einer gegebenen Initialisiererklausel vor jeder Wertberechnung und jedem Seiteneffekt sequenziert, die mit einer nachfolgenden Initialisiererklausel in der geschweiften Klammern eingeschlossenen kommaseparierten Liste von Initialisierern verbunden sind.
Ein Funktionsaufruf, der nicht vor oder nach einer anderen Ausdrucksauswertung außerhalb der Funktion sequenziert ist (möglicherweise ein anderer Funktionsaufruf), ist indeterminately sequenced mit dieser Auswertung (das Programm muss sich so verhalten, als ob die CPU-Instruktionen, die einen Funktionsaufruf bilden, nicht mit den Instruktionen verschachtelt wären, die Auswertungen anderer Ausdrücke, einschließlich anderer Funktionsaufrufe, bilden, selbst wenn die Funktion inline war).
Regel 10 hat eine Ausnahme: Funktionsaufrufe, die von einem Standardbibliotheksalgorithmus unter Verwendung der Ausführungsrichtlinie std::execution::par_unseq ausgeführt werden, sind unsequenziert und können beliebig miteinander verschachtelt werden. (seit C++17)
11) Der Aufruf der Allokationsfunktion (operator new) ist indeterminately sequenced mit(bis C++17)sequenced before(seit C++17) der Auswertung der Konstruktorargumente in einem new-Ausdruck.
12) Beim Zurückkehren aus einer Funktion ist die Kopierinitialisierung des temporären Objekts, das das Ergebnis der Auswertung des Funktionsaufrufs ist, vor der Zerstörung aller temporären Objekte am Ende des Operanden der return-Anweisung sequenziert, was wiederum vor der Zerstörung lokaler Variablen des Blocks sequenziert ist, der die return-Anweisung umschließt.
13) In einem Funktionsaufrufausdruck wird der Ausdruck, der die Funktion benennt, vor jedem Argumentausdruck und jedem Standardargument sequenziert.
14) Bei einem Funktionsaufruf sind die Wertberechnungen und Seiteneffekte der Initialisierung jedes Parameters indeterminately sequenced mit den Wertberechnungen und Seiteneffekten jedes anderen Parameters.
15) Jeder überladene Operator befolgt die Sequenzierungsregeln des integrierten Operators, den er bei der Verwendung von Operatornotation überlädt.
16) In einem Subskriptausdruck E1[E2] wird E1 vor E2 sequenziert.
17) In einem Zeiger-auf-Mitglied-Ausdruck E1.*E2 oder E1->*E2 wird E1 vor E2 sequenziert (es sei denn, der dynamische Typ von E1 enthält nicht das Mitglied, auf das sich E2 bezieht).
18) In einem Shift-Operator-Ausdruck 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 kommaseparierten Liste von Ausdrücken in einem parenthetischen Initialisierer wird so ausgewertet, als wäre es ein Funktionsaufruf (indeterminately-sequenced).
(seit C++17)

[bearbeiten] Undefined behavior (nicht definiertes Verhalten)

Das Verhalten ist in den folgenden Fällen nicht definiert

1) Ein Seiteneffekt auf einen Speicherort ist relativ zu einem anderen Seiteneffekt auf denselben Speicherort unsequenziert.
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 Seiteneffekt auf einen Speicherort ist relativ zu einer Wertberechnung, die den Wert eines Objekts an demselben Speicherort verwendet, unsequenziert.
cout << i << i++; // undefined behavior until C++17
a[i] = i++;       // undefined behavior until C++17
n = ++i + i;      // undefined behavior
3) Das Starten oder Beenden der Lebensdauer eines Objekts an einem Speicherort ist relativ zu einer der folgenden Operationen unsequenziert:
  • ein Seiteneffekt am selben Speicherort
  • eine Wertberechnung, die den Wert eines Objekts an demselben Speicherort verwendet
  • das Starten oder Beenden der Lebensdauer eines Objekts, das Speicherplatz belegt, der mit dem Speicherort überlappt.
union U { int x, y; } u;
(u.x = 1, 0) + (u.y = 2, 0); // undefined behavior

[bearbeiten] Sequenzpunkt-Regeln (bis C++11)

[bearbeiten] Definitionen vor C++11

Die Auswertung eines Ausdrucks kann Seiteneffekte hervorrufen, die sind: Zugriff auf ein Objekt, das durch einen volatile-lvalue bezeichnet wird, Modifikation eines Objekts, Aufruf einer Bibliotheks-I/O-Funktion oder Aufruf einer Funktion, die eine dieser Operationen durchführt.

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

[bearbeiten] Regeln vor C++11

1) Am Ende jedes vollständigen Ausdrucks (typischerweise am Semikolon) befindet sich ein Sequenzpunkt.
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), die vor der Ausführung von Ausdrücken oder Anweisungen im Funktionskörper stattfindet.
3) Beim Zurückkehren 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 Ausdrucks in der return-Anweisung (falls vorhanden).
4) Es gibt einen Sequenzpunkt nach dem Kopieren eines Rückgabewerts einer Funktion 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 werden).
6) Bei der Auswertung jedes der folgenden vier Ausdrücke unter Verwendung der integrierten (nicht überladenen) Operatoren gibt es nach der Auswertung des Ausdrucks a einen Sequenzpunkt.
a && b
a || b
a ? b : c
a , b

[bearbeiten] Nicht definiertes Verhalten vor C++11

Das Verhalten ist in den folgenden Fällen nicht definiert

1) Zwischen dem vorherigen und dem nächsten Sequenzpunkt wird der Wert eines Objekts an einem Speicherort mehr als einmal durch die Auswertung eines Ausdrucks modifiziert.
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 modifiziert wird, sein vorheriger Wert in einer anderen Weise als zur Bestimmung des zu speichernden Werts zugegriffen.
cout << i << i++; // undefined behavior
a[i] = i++;       // undefined behavior

[bearbeiten] Defect reports

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 Sequenzierung der Zerstörung von automatischen
Variablen bei Funktionsrückgabe war nicht explizit
Sequenzierungsregeln hinzugefügt
CWG 1949 C++11 "sequenced after" wurde verwendet, aber nicht im C++-Standard definiert. definiert als die Umkehrung
von "sequenced before"
CWG 1953 C++11 Seiteneffekte und Wertberechnungen, die einen Speicherort betreffen,
konnten relativ zum Starten oder Beenden der
Lebensdauer eines Objekts am selben Speicherort unsequenziert sein.
Das Verhalten ist
in diesem Fall nicht definiert.
CWG 2146 C++98 Die Fälle, die nicht definiertes Verhalten betrafen, berücksichtigten keine Bitfelder. berücksichtigt.

[bearbeiten] Referenzen

  • C++23 Standard (ISO/IEC 14882:2024)
  • 6.9.1 Program execution [intro.execution]
  • 7.6.1.6 Increment and decrement [expr.post.incr]
  • 7.6.2.8 New [expr.new]
  • 7.6.14 Logical AND operator [expr.log.and]
  • 7.6.15 Logical OR operator [expr.log.or]
  • 7.6.16 Conditional operator [expr.cond]
  • 7.6.19 Assignment and compound assignment operators [expr.ass]
  • 7.6.20 Comma operator [expr.comma]
  • 9.4.5 List-initialization [dcl.init.list]
  • C++20 Standard (ISO/IEC 14882:2020)
  • 6.9.1 Program execution [intro.execution]
  • 7.6.1.5 Increment and decrement [expr.post.incr]
  • 7.6.2.7 New [expr.new]
  • 7.6.14 Logical AND operator [expr.log.and]
  • 7.6.15 Logical OR operator [expr.log.or]
  • 7.6.16 Conditional operator [expr.cond]
  • 7.6.19 Assignment and compound assignment operators [expr.ass]
  • 7.6.20 Comma operator [expr.comma]
  • 9.4.4 List-initialization [dcl.init.list]
  • C++17 Standard (ISO/IEC 14882:2017)
  • 4.6 Program execution [intro.execution]
  • 8.2.6 Increment and decrement [expr.post.incr]
  • 8.3.4 New [expr.new]
  • 8.14 Logical AND operator [expr.log.and]
  • 8.15 Logical OR operator [expr.log.or]
  • 8.16 Conditional operator [expr.cond]
  • 8.18 Assignment and compound assignment operators [expr.ass]
  • 8.19 Comma operator [expr.comma]
  • 11.6.4 List-initialization [dcl.init.list]
  • C++14 Standard (ISO/IEC 14882:2014)
  • 1.9 Program execution [intro.execution]
  • 5.2.6 Increment and decrement [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Logical AND operator [expr.log.and]
  • 5.15 Logical OR operator [expr.log.or]
  • 5.16 Conditional operator [expr.cond]
  • 5.17 Assignment and compound assignment operators [expr.ass]
  • 5.18 Comma operator [expr.comma]
  • 8.5.4 List-initialization [dcl.init.list]
  • C++11 Standard (ISO/IEC 14882:2011)
  • 1.9 Program execution [intro.execution]
  • 5.2.6 Increment and decrement [expr.post.incr]
  • 5.3.4 New [expr.new]
  • 5.14 Logical AND operator [expr.log.and]
  • 5.15 Logical OR operator [expr.log.or]
  • 5.16 Conditional operator [expr.cond]
  • 5.17 Assignment and compound assignment operators [expr.ass]
  • 5.18 Comma operator [expr.comma]
  • 8.5.4 List-initialization [dcl.init.list]

[bearbeiten] Siehe auch

  • Operator-Priorität, die definiert, wie Ausdrücke aus ihrer Quellcode-Darstellung aufgebaut werden.
C-Dokumentation für Reihenfolge der Auswertung