wake-up-neo.com

Undefinierte Verhaltens- und Sequenzpunkte

Was sind "Sequenzpunkte"?

Wie ist das Verhältnis zwischen undefiniertem Verhalten und Sequenzpunkten?

Ich benutze oft lustige und verworrene Ausdrücke wie a[++i] = i;, um mich besser zu fühlen. Warum sollte ich damit aufhören?

Wenn Sie dies gelesen haben, besuchen Sie unbedingt die Folgefrage UNDEFINIERTE VERHALTENS- UND SEQUENZPUNKTE NEU GELADEN.

(Hinweis: Dies ist ein Eintrag für Stack Overflow's C++ FAQ . Wenn Sie die Vorstellung eines FAQ kritisieren möchten, In dieser Form wäre dann das Posting auf Meta, das all dies begann der Ort dafür. Die Antworten auf diese Frage werden im C++ - Chatroom überwacht, wo die FAQ Die Idee hat anfangs angefangen, so dass Ihre Antwort wahrscheinlich von denjenigen gelesen wird, die mit der Idee gekommen sind.)

931
Prasoon Saurav

C++ 98 und C++ 03

Diese Antwort gilt für ältere Versionen des C++ - Standards. Die Versionen C++ 11 und C++ 14 des Standards enthalten formal keine 'Sequenzpunkte'. Operationen werden stattdessen "vorher sequenziert" oder "nicht sequenziert" oder "unbestimmt sequenziert". Der Nettoeffekt ist im Wesentlichen der gleiche, aber die Terminologie ist unterschiedlich.


Haftungsausschluss: Okay. Diese Antwort ist etwas lang. Also haben Sie Geduld beim Lesen. Wenn Sie diese Dinge bereits kennen, werden Sie nicht verrückt, wenn Sie sie noch einmal lesen.

Voraussetzungen: Grundkenntnisse in C++ Standard


Was sind Sequenzpunkte?

Der Standard sagt

An bestimmten festgelegten Punkten in der Ausführungssequenz, die als Sequenzpunkte bezeichnet werden, müssen alle Nebenwirkungen früherer Bewertungen vollständig sein und dürfen keine Seite aufweisen Auswirkungen nachfolgender Bewertungen haben stattgefunden. (§1.9/7)

Nebenwirkungen? Was sind Nebenwirkungen?

Die Auswertung eines Ausdrucks erzeugt etwas, und wenn sich zusätzlich der Zustand der Ausführungsumgebung ändert, heißt es, dass der Ausdruck (seine Auswertung) einige Nebenwirkungen hat.

Zum Beispiel:

int x = y++; //where y is also an int

Zusätzlich zur Initialisierungsoperation wird der Wert von y aufgrund der Nebenwirkung des Operators ++ geändert.

So weit, ist es gut. Weiter zu Sequenzpunkten. Eine vom comp.lang.c-Autor angegebene alternative Definition von seq-Punkten Steve Summit:

Der Sequenzpunkt ist ein Zeitpunkt, an dem sich der Staub abgesetzt hat und alle bisher beobachteten Nebenwirkungen garantiert vollständig sind.


Was sind die im C++ - Standard aufgeführten allgemeinen Sequenzpunkte?

Diese sind:

  • am Ende der Auswertung des vollständigen Ausdrucks (§1.9/16) (Ein vollständiger Ausdruck ist ein Ausdruck, der kein Unterausdruck eines anderen Ausdrucks ist.)1

    Beispiel:

    int a = 5; // ; is a sequence point here
    
  • bei der Auswertung der folgenden Ausdrücke nach der Auswertung des ersten Ausdrucks (§1.9/182

    • a && b (§5.14)
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18) (hier ist a, b ein Kommaoperator; in func(a,a++) ist , kein Kommaoperator, sondern lediglich ein Trennzeichen zwischen den Argumenten a und a++. Somit ist das Verhalten darin undefiniert case (wenn a als primitiver Typ betrachtet wird))
  • bei einem Funktionsaufruf (unabhängig davon, ob die Funktion inline ist oder nicht) nach der Auswertung aller Funktionsargumente (falls vorhanden), die vor der Ausführung von Ausdrücken oder Anweisungen im Funktionskörper (§1.9/17) ausgeführt werden.

1: Hinweis: Die Auswertung eines vollständigen Ausdrucks kann die Auswertung von Unterausdrücken umfassen, die nicht lexikalisch Teil des vollständigen Ausdrucks sind. Beispielsweise wird angenommen, dass Unterausdrücke, die an der Auswertung von Standardargumentausdrücken (8.3.6) beteiligt sind, in dem Ausdruck erstellt werden, der die Funktion aufruft, und nicht in dem Ausdruck, der das Standardargument definiert

2: Die angegebenen Operatoren sind die in Klausel 5 beschriebenen eingebauten Operatoren. Wenn einer dieser Operatoren in einem gültigen Kontext überladen ist (Klausel 13) und somit eine benutzerdefinierte Operatorfunktion bezeichnet, bezeichnet der Ausdruck einen Funktionsaufruf und Die Operanden bilden eine Argumentliste, zwischen denen sich kein impliziter Sequenzpunkt befindet.


Was ist undefiniertes Verhalten?

Der Standard definiert Undefiniertes Verhalten in Abschnitt §1.3.12 als

verhalten, wie es bei Verwendung eines fehlerhaften Programmkonstrukts oder fehlerhafter Daten auftreten kann, für die diese Internationale Norm keine Anforderungen vorschreibt 3.

Undefiniertes Verhalten ist auch zu erwarten, wenn in dieser Internationalen Norm keine explizite Definition des Verhaltens enthalten ist.

3: Das zulässige undefinierte Verhalten reicht vom vollständigen Ignorieren der Situation mit unvorhersehbaren Ergebnissen über das Verhalten während der Übersetzung oder Programmausführung in einer dokumentierten, für die Umgebung charakteristischen Art und Weise (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Beenden einer Übersetzung oder Ausführung (mit der Ausgabe einer Diagnosemeldung).

Kurz gesagt, undefiniertes Verhalten bedeutet alles kann passieren, wenn Dämonen aus Ihrer Nase fliegen und Ihre Freundin schwanger wird.


Welche Beziehung besteht zwischen undefiniertem Verhalten und Sequenzpunkten?

Bevor ich darauf eingehe, müssen Sie den Unterschied zwischen ndefiniertes Verhalten, nicht festgelegtes Verhalten und implementierungsdefiniertes Verhalten kennen.

Sie müssen auch das the order of evaluation of operands of individual operators and subexpressions of individual expressions, and the order in which side effects take place, is unspecified kennen.

Zum Beispiel:

int x = 5, y = 6;

int z = x++ + y++; //it is unspecified whether x++ or y++ will be evaluated first.

Ein weiteres Beispiel hier .


Nun sagt der Standard in §5/4

  • 1) Zwischen dem vorherigen und dem nächsten Sequenzpunkt darf der gespeicherte Wert eines skalaren Objekts höchstens einmal durch Auswertung eines Ausdrucks geändert werden.

Was heißt das?

Informell bedeutet dies, dass zwischen zwei Sequenzpunkten eine Variable nicht mehr als einmal geändert werden darf. In einer Ausdrucksanweisung steht next sequence point normalerweise am abschließenden Semikolon und previous sequence point am Ende der vorherigen Anweisung. Ein Ausdruck kann auch das Zwischenprodukt sequence points enthalten.

Ab dem obigen Satz rufen die folgenden Ausdrücke Undefiniertes Verhalten auf:

i++ * ++i;   // UB, i is modified more than once btw two SPs
i = ++i;     // UB, same as above
++i = 2;     // UB, same as above
i = ++i + 1; // UB, same as above
++++++i;     // UB, parsed as (++(++(++i)))

i = (i, ++i, ++i); // UB, there's no SP between `++i` (right most) and assignment to `i` (`i` is modified more than once btw two SPs)

Aber die folgenden Ausdrücke sind in Ordnung:

i = (i, ++i, 1) + 1; // well defined (AFAIK)
i = (++i, i++, i);   // well defined 
int j = i;
j = (++i, i++, j*i); // well defined

  • 2) Außerdem darf auf den vorherigen Wert nur zugegriffen werden, um den zu speichernden Wert zu bestimmen.

Was heißt das? Wenn ein Objekt innerhalb eines vollständigen Ausdrucks beschrieben wird, bedeutet dies, dass alle Zugriffe auf das Objekt innerhalb desselben Ausdrucks muss direkt an der Berechnung des zu schreibenden Werts beteiligt sein.

Zum Beispiel sind in i = i + 1 alle Zugriffe von i (in L.H.S und in R.H.S) direkt an der Berechnung beteiligt ​​des zu schreibenden Werts. Also ist es gut.

Diese Regel schränkt die rechtlichen Ausdrücke wirksam auf diejenigen ein, bei denen die Zugriffe nachweislich vor der Änderung liegen.

Beispiel 1:

std::printf("%d %d", i,++i); // invokes Undefined Behaviour because of Rule no 2

Beispiel 2:

a[i] = i++ // or a[++i] = i or a[i++] = ++i etc

ist nicht zulässig, weil einer der Zugriffe auf i (der in a[i]) nichts mit dem Wert zu tun hat, der in i gespeichert wird (was in i++ vorkommt), und Es gibt also keine gute Möglichkeit, zu definieren, ob der Zugriff vor oder nach dem Speichern des inkrementierten Werts erfolgen soll - weder für unser Verständnis noch für den Compiler. Das Verhalten ist also undefiniert.

Beispiel 3:

int x = i + i++ ;// Similar to above

Follow-up-Antwort für C++ 11 hier .

668
Prasoon Saurav

Dies ist eine Fortsetzung meiner vorherigen Antwort ​​und enthält C++ 11-bezogene Inhalte. .


Voraussetzungen : Grundkenntnisse in Relationen (Mathematik).


Stimmt es, dass es in C++ 11 keine Sequenzpunkte gibt?

Ja! Das ist sehr wahr.

Sequenzpunkte wurden ersetzt durch Sequenziert vor und Sequenziert nach (und Unsequenced und Unbestimmt Sequenced ) relations in C++ 11.


Was genau ist dieses "Sequenziert vor" -Ding?

Sequenced Before (§1.9/13) ist eine Beziehung, die ist:

zwischen Auswertungen, die von einem einzelnen thread ausgeführt werden, und induziert eine strenge Teilreihenfolge 1

Formal bedeutet es, dass zwei Bewertungen gegeben sind(Siehe unten) A und B, wenn A sequenziert vor B ist, muss die Ausführung von A der Ausführung von B vorangehen. Wenn A nicht vor B und B nicht vor A sequenziert wird, sind A und B nicht sequenziert  2.

Auswertungen A und B sind unbestimmt sequenziert , wenn entweder A vor B oder B vor A sequenziert ist, aber nicht angegeben ist, welche3.

[ANMERKUNGEN]
1: Eine strikte Teilreihenfolge ist eine binäre Beziehung "<" über eine Menge P, die asymmetric ist und transitive , dh für alle a, b und c in P gilt Folgendes:
........(ich). wenn a <b dann ¬ (b <a) (asymmetry);
........ (ii). wenn a <b und b <c dann a <c (transitivity).
2: Die Ausführung von nicht sequenzierten Auswertungen kann überlappen .
3: Unbestimmt sequenzierte Auswertungen können sich nicht überlappen , aber beide können zuerst ausgeführt werden.


Was bedeutet das Wort "Evaluation" im Kontext von C++ 11?

In C++ 11 umfasst die Auswertung eines Ausdrucks (oder eines Unterausdrucks) im Allgemeinen:

  • - Wertberechnungen (einschließlich der Ermittlung der Identität eines Objekts für glvalue evaluation und Abrufen eines zuvor einem Objekt zugewiesenen Werts für prvalue evaluation =) und

  • einleitung von Nebenwirkungen .

Jetzt (§1.9/14) heißt es:

Jede mit einem vollständigen Ausdruck verbundene Wertberechnung und Nebenwirkung wird sequenziert, bevor jede mit dem nächsten auszuwertenden vollständigen Ausdruck assoziierte Wertberechnung und Nebenwirkung .

  • Triviales Beispiel:

    int x;x = 10;++x;

    Die mit ++x verbundene Wertberechnung und Nebenwirkung wird nach der Wertberechnung und Nebenwirkung von x = 10; sequenziert.


Es muss also einen Zusammenhang zwischen Undefiniertem Verhalten und den oben genannten Dingen geben, oder?

Ja! Richtig.

In (§1.9/15) wurde dies erwähnt

Sofern nicht anders angegeben, sind Auswertungen von Operanden einzelner Operatoren und von Unterausdrücken einzelner Ausdrücke unsequenziert 4.

Zum Beispiel :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Die Auswertung der Operanden des Operators + erfolgt relativ zueinander ohne Reihenfolge.
  2. Die Auswertung der Operanden der Operatoren << und >> erfolgt unabhängig voneinander.

4: In einem Ausdruck, der während der Ausführung eines Programms mehrmals ausgewertet wird, werden unsequenziert und unbestimmt sequenziert Auswertungen seiner Unterausdrücke müssen nicht konsistent in unterschiedlichen Auswertungen durchgeführt werden.

(§1.9/15) Die Wertberechnungen der Operanden eines Operators werden vor der Wertberechnung des Ergebnisses des Operators sequenziert.

Das heißt, in x + y wird die Werteberechnung von x und y vor der Werteberechnung von (x + y) sequenziert.

Wichtiger

(§1.9/15) Wenn eine Nebenwirkung auf ein skalares Objekt in Bezug auf eine der beiden Folgen nicht vorliegt

(a) eine weitere Nebenwirkung auf dasselbe skalare Objekt

oder

(b) eine Wertberechnung mit dem Wert desselben skalaren Objekts.

das Verhalten ist undefined .

Beispiele:

int i = 5, v[10] = { };
void  f(int,  int);
  1. i = i++ * ++i; // Undefined Behaviour
  2. i = ++i + i++; // Undefined Behaviour
  3. i = ++i + ++i; // Undefined Behaviour
  4. i = v[i++]; // Undefined Behaviour
  5. i = v[++i]: // Well-defined Behavior
  6. i = i++ + 1; // Undefined Behaviour
  7. i = ++i + 1; // Well-defined Behaviour
  8. ++++i; // Well-defined Behaviour
  9. f(i = -1, i = -1); // Undefined Behaviour (see below)

Beim Aufrufen einer Funktion (unabhängig davon, ob die Funktion inline ist oder nicht) wird jede Wertberechnung und jeder Nebeneffekt, die mit einem Argumentausdruck oder mit dem Postfixausdruck verknüpft sind, der die aufgerufene Funktion bezeichnet, vor der Ausführung jedes Ausdrucks oder jeder Anweisung im Hauptteil der sequenziert Funktion aufgerufen. [ Hinweis: Wertberechnungen und Nebenwirkungen, die mit verschiedenen Argumentausdrücken verknüpft sind, sind nicht sequenziert . - Endnote ]

Ausdrücke (5), (7) und (8) rufen kein undefiniertes Verhalten auf. In den folgenden Antworten finden Sie eine ausführlichere Erläuterung.


Final Note :

Wenn Sie einen Fehler in der Post finden, hinterlassen Sie bitte einen Kommentar. Power-User (Mit rep> 20000) zögern bitte nicht, den Beitrag zu bearbeiten, um Tippfehler und andere Fehler zu korrigieren.

273
Prasoon Saurav

C++ 17 (N4659) enthält einen Vorschlag Verfeinern der Ausdrucksauswertungsreihenfolge für idiomatisches C++ , der eine strengere Reihenfolge der Ausdrucksauswertung definiert.

Insbesondere wurde der folgender Satz hinzugefügt:

8.18 Zuweisungs- und zusammengesetzte Zuweisungsoperatoren :
....

In allen Fällen wird die Zuweisung nach der Wertberechnung des rechten und linken Operanden und vor der Wertberechnung des Zuweisungsausdrucks sequenziert. Der rechte Operand wird vor dem linken Operanden sequenziert.

Es macht mehrere Fälle von zuvor undefiniertem Verhalten gültig, einschließlich des fraglichen:

a[++i] = i;

Einige andere ähnliche Fälle führen jedoch immer noch zu undefiniertem Verhalten.

Im N4140:

i = i++ + 1; // the behavior is undefined

Aber in N4659

i = i++ + 1; // the value of i is incremented
i = i++ + i; // the behavior is undefined

Die Verwendung eines C++ 17-kompatiblen Compilers bedeutet natürlich nicht unbedingt, dass man solche Ausdrücke schreiben sollte.

24
AlexD

Ich vermute, es gibt einen grundlegenden Grund für die Änderung. Es ist nicht nur eine kosmetische Sache, die alte Interpretation klarer zu machen: Dieser Grund ist Gleichzeitigkeit. Eine nicht festgelegte Reihenfolge der Ausarbeitung ist lediglich die Auswahl einer von mehreren möglichen Serienreihenfolgen, dies unterscheidet sich ziemlich von vor und nach der Reihenfolge, denn wenn keine vorgegebene Reihenfolge vorliegt, ist eine gleichzeitige Bewertung möglich: Nicht bei den alten Regeln. Zum Beispiel in:

f (a,b)

vorher entweder a dann b oder b dann a. Nun können a und b mit verschachtelten Anweisungen oder sogar auf verschiedenen Kernen ausgewertet werden. 

11
Yttrill

In C99(ISO/IEC 9899:TC3), das in dieser Diskussion bisher nicht vorhanden zu sein scheint, werden die folgenden Aussagen bezüglich der Reihenfolge der Bewertung gemacht.

[...] die Reihenfolge der Auswertung von Teilausdrücken und die Reihenfolge, in der Nebenwirkungen treten auf, beide sind nicht spezifiziert. (Abschnitt 6.5, S. 67)

Die Reihenfolge der Auswertung der Operanden ist nicht spezifiziert. Wenn ein Versuch wird gemacht, um das Ergebnis eines Zuweisungsoperators zu ändern oder darauf zuzugreifen Nach dem nächsten Sequenzpunkt ist das Verhalten nicht definiert (Abschnitt 6.5.16 S. 91).

0
awiebe