Was sind Sequenzpunkte in C++ und was haben sie mit undefiniertem Verhalten zu tun?

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Die geheime Waffe des Debuggers: Was C++-Programmierer übersehen

\n\n

Haben Sie jemals so etwas wie i = i++ + ++i; geschrieben und mit verschiedenen Compilern unterschiedliche Ergebnisse erhalten? Oder einen seltsamen Fehler erwischt, der nur auf dem Server, aber nicht auf Ihrem Laptop reproduzierbar war? Lernen Sie kennen: undefined behavior (undefiniertes Verhalten) – einer der heimtückischsten Feinde eines C++-Entwicklers. Und im Zentrum dieses Chaos stehen Sequenzpunkte (sequence points).

\n\n

In diesem Artikel erklären wir, was Sequenzpunkte sind, wie sie mit der Auswertungsreihenfolge in C++ zusammenhängen und – am wichtigsten – wie man diese Fallstricke vermeidet. Der Beitrag basiert auf einer legendären Frage auf Stack Overflow, die tausende Aufrufe erhalten hat. Los geht's!

\n\n

Was sind Sequenzpunkte (sequence points)?

\n\n

Stellen Sie sich vor, Sie sind Dirigent eines Orchesters. Jeder Musiker (Prozessorbefehl) muss zu einem genau festgelegten Zeitpunkt einsetzen. Ein Sequenzpunkt ist ein Zeitpunkt, zu dem alle vorherigen „Nebeneffekte“ (Änderungen von Variablen, Funktionsaufrufe) garantiert abgeschlossen sind und die nächsten noch nicht begonnen haben.

\n\n

Im C++-Standard 98/03 wurde der Begriff sequence point verwendet. In C++11 wurde er durch die strengeren Beziehungen „sequenced before / sequenced after“ ersetzt, aber das Prinzip bleibt gleich: Zwischen zwei Sequenzpunkten darf der Compiler Operationen nach Belieben umordnen, solange das Ergebnis (gemäß der „as-if“-Regel) unverändert bleibt.

\n\n

Einfach ausgedrückt: Wenn Sie versuchen, dieselbe Variable zwischen zwei Sequenzpunkten zweimal zu ändern, erhalten Sie undefiniertes Verhalten. Der Compiler kann alles Mögliche tun: von einem korrekten Ergebnis bis zur Formatierung Ihrer Festplatte (Scherz, aber fast wahr).

\n\n

Klassische Beispiele für Sequenzpunkte

\n
    \n
  • Das Semikolon ; – Ende eines Ausdrucks
  • \n
  • Das Komma , als Operator (aber nicht in Funktionsargumenten!)
  • \n
  • Das Ende jedes Ausdrucks in einer for-Schleife (nach Initialisierung, Prüfung und Schritt)
  • \n
  • Die logischen Operatoren && und ||
  • \n
  • Der ternäre Operator ?:
  • \n
  • Ein Funktionsaufruf (bevor der Funktionskörper ausgeführt wird)
  • \n
\n\n

Wie erzeugen Sequenzpunkte undefiniertes Verhalten?

\n\n

Hier ist ein klassisches Beispiel, das jeder C++-Entwickler kennen sollte:

\n\n
int i = 0;\ni = i++ + ++i;  // UB! Undefiniertes Verhalten
\n\n

Warum ist das UB? Weil zwischen zwei Sequenzpunkten (in diesem Fall zwischen Anfang und Ende des Ausdrucks) die Variable i zweimal geändert wird:

\n
    \n
  1. Postfix-Inkrement i++
  2. \n
  3. Präfix-Inkrement ++i
  4. \n
  5. Zuweisung i = ...
  6. \n
\n\n

Der Compiler weiß nicht, in welcher Reihenfolge diese Operationen ausgeführt werden sollen. Das Ergebnis? Ein Compiler liefert 2, ein anderer 3, ein dritter 0. Und im Optimierungsmodus könnte dieser Code sogar komplett entfernt werden.

\n\n

Ein weiterer kniffliger Fall: Funktionsargumente

\n\n
int foo(int a, int b) { return a + b; }\n\nint x = 5;\nint result = foo(++x, ++x);  // UB!
\n\n

Hier ist das Komma zwischen den Argumenten kein Komma-Operator, sondern ein Trennzeichen. Es gibt keinen Sequenzpunkt zwischen der Auswertung der Argumente ++x! Beide Inkremente werden (aus Sicht des Standards) „parallel“ ausgeführt, und wir erhalten erneut undefiniertes Verhalten.

\n\n

Praktische Beispiele: Wie sieht UB in echtem Code aus?

\n\n

Beispiel 1: Nicht offensichtlicher Fehler bei der Indizierung

\n\n
int arr[5] = {0, 10, 20, 30, 40};\nint idx = 1;\narr[idx] = idx++;  // UB!
\n\n

Auf den ersten Blick einfach: Wir möchten 1 in arr[1] schreiben und dann idx erhöhen. Der Standard garantiert jedoch nicht die Reihenfolge der Auswertung des Index idx und der rechten Seite idx++. Das Ergebnis? Es könnte in arr[1] oder in arr[2] geschrieben werden.

\n\n

Beispiel 2: Kompl

Blogs

Buchempfehlungen