Frage Nicht definierte Verhaltens- und Sequenzpunkte


Was sind "Sequenzpunkte"?

Was ist die Beziehung zwischen undefiniertem Verhalten und Sequenzpunkten?

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

Wenn Sie dies gelesen haben, besuchen Sie die Folgefrage Nicht definierte Verhaltens- und Sequenzpunkte wurden neu geladen.

(Hinweis: Dies ist ein Eintrag zu Die C ++ - FAQ von Stack Overflow. Wenn Sie die Idee einer FAQ in diesem Formular kritisieren möchten, dann das Posting auf Meta, mit dem alles begann wäre der richtige Ort dafür. Antworten auf diese Frage werden in der C ++ Chatroom, wo die FAQ-Idee von Anfang an begann, also wird Ihre Antwort sehr wahrscheinlich von denen gelesen, die auf die Idee gekommen sind.)


897


Ursprung


Antworten:


C ++ 98 und C ++ 03

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


Haftungsausschluss : Okay. Diese Antwort ist ein bisschen lang. Also habe Geduld beim Lesen. Wenn du diese Dinge bereits kennst, wirst du sie nicht wieder verrückt machen.

Voraussetzungen : Ein elementares Wissen über C ++ Standard 


Was sind Sequenzpunkte?

Der Standard sagt

An bestimmten angegebenen Punkten in der Ausführungsreihenfolge aufgerufen Sequenzpunkte, alle Nebenwirkungen von früheren Bewertungen   soll vollständig sein und nein Nebenwirkungen von späteren Bewertungen müssen stattgefunden haben. (§1.9 / 7)

Nebenwirkungen? Was sind Nebenwirkungen?

Die Auswertung eines Ausdrucks erzeugt etwas, und wenn sich zusätzlich der Zustand der Ausführungsumgebung ändert, wird gesagt, dass der Ausdruck (seine Bewertung) einige Nebeneffekte hat.

Beispielsweise:

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

Zusätzlich zur Initialisierungsoperation wird der Wert von y verändert sich aufgrund der Nebenwirkung von ++ Operator.

So weit, ist es gut. Weiter zu Sequenzpunkten. Eine Alternationsdefinition von Seq-Punkten, die vom Autor comp.lang.c gegeben wird Steve Summit:

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


Was sind die allgemeinen Sequenzpunkte, die im C ++ Standard aufgeführt sind?

Diese sind:

  • am Ende der Bewertung der vollen Ausdruck (§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 Bewertung jedes der folgenden Ausdrücke nach der Auswertung des ersten Ausdrucks (§1.9/18) 2

    • a && b (§5.14) 
    • a || b (§5.15)
    • a ? b : c (§5.16)
    • a , b (§5.18) (hier ist a, b ein Kommaoperator; func(a,a++)  , ist kein Komma-Operator, es ist lediglich ein Trennzeichen zwischen den Argumenten aund a++. Daher ist das Verhalten in diesem Fall nicht definiert (wenn a wird als primitiver Typ betrachtet))
  • bei einem Funktionsaufruf (ob die Funktion inline ist), nach der Auswertung aller Funktionsargumente (falls vorhanden) welche erfolgt vor der Ausführung von Ausdrücken oder Anweisungen im Funktionsbaustein (§1.9/17).

1: Hinweis: Die Auswertung eines Full-Expressions kann die Auswertung von Teilausdrücken beinhalten, die nicht lexikalisch sind Teil des vollen Ausdrucks. Beispielsweise werden Teilausdrücke, die an der Auswertung von Argumenten für Standardargumente (8.3.6) beteiligt sind, als in dem Ausdruck erstellt betrachtet, der die Funktion aufruft, und nicht als Ausdruck, der das Standardargument definiert

2: Die angegebenen Operatoren sind die eingebauten Operatoren, wie in Abschnitt 5 beschrieben. 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 ohne einen impliziten Sequenzpunkt zwischen ihnen.


Was ist ein nicht definiertes Verhalten?

Der Standard definiert nicht definiertes Verhalten in Abschnitt §1.3.12 wie

Verhalten, wie es bei Verwendung eines fehlerhaften Programmkonstrukts oder fehlerhafter Daten auftreten könnte, für die dieser Internationale Standard gilt keine Anforderungen 3.

Ein nicht definiertes Verhalten kann ebenfalls erwartet werden   Der Internationale Standard lässt die Beschreibung einer expliziten Definition von Verhalten aus.

 3: Zulässiges undefiniertes Verhalten reicht von einer vollständigen Ignorierung der Situation mit unvorhersehbaren Ergebnissen bis hin zu einem Verhalten bei der Übersetzung oder Programmausführung in einer dokumentierten Weise, die für die Umgebung charakteristisch ist (mit oder ohne die Ausgabe einer Diagnosemeldung), zum Beenden einer Übersetzung oder Ausführung (mit der Ausgabe einer Diagnosemeldung).

Kurz gesagt, undefiniertes Verhalten bedeutet etwas kann passieren, wenn Dämonen aus deiner Nase fliegen und deine Freundin schwanger wird.


Was ist die Beziehung zwischen nicht definiertem Verhalten und Sequenzpunkten?

Bevor ich darauf eingehe, müssen Sie den Unterschied zwischen beiden kennen Undefiniertes Verhalten, nicht spezifiziertes Verhalten und definiertes Verhalten der Implementierung.

Das musst du auch wissen 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.

Beispielsweise:

int x = 5, y = 6;

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

Ein anderes Beispiel Hier.


Jetzt der Standard in §5/4 sagt

  • 1) Zwischen dem vorherigen und dem nächsten Sequenzpunkt darf der Wert eines skalaren Objekts höchstens einmal durch die 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 Ausdruckserklärung, die next sequence point befindet sich normalerweise am abschließenden Semikolon und der previous sequence point ist am Ende der vorherigen Aussage. Ein Ausdruck kann auch Zwischenprodukte enthalten sequence points.

Aus 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) Darüber hinaus soll auf den früheren Wert nur zugegriffen werden, um den Wert zu bestimmen, der gespeichert werden soll.

Was heißt das? Das bedeutet, wenn ein Objekt in einen vollständigen Ausdruck geschrieben wird, alle Zugriffe auf denselben innerhalb desselben Ausdrucks muss direkt an der Berechnung des zu schreibenden Wertes beteiligt sein.

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

Diese Regel schränkt legale Ausdrücke effektiv auf solche ein, in denen die Zugriffe nachweislich vor der Änderung stehen.

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 erlaubt, da einer der Zugriffe von i (der eine in a[i]) hat nichts mit dem Wert zu tun, der in i gespeichert wird (was in i++), und so gibt es keine gute Möglichkeit zu definieren - entweder für unser Verständnis oder den Compiler -, ob der Zugriff stattfinden soll, bevor oder nachdem der inkrementierte Wert gespeichert wird. Das Verhalten ist also nicht definiert.

Beispiel 3:

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

Folgeantwort Hier. 


623



Dies ist ein Follow-up zu meinem vorherige Antwort und enthält C ++ 11 verwandtes Material..


Voraussetzungen : Ein elementares Wissen der Beziehungen (Mathematik).


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

Ja! Das ist sehr wahr.

Sequenzpunkte wurden ersetzt durch Vorher sequenziertund Nachher sequenziert (und Nicht sequenziertund Unbestimmt sequenziert) Beziehungen in C ++ 11.


Was genau ist das 'Sequenced Before' Ding?

Vorher sequenziert(§1.9 / 13) ist eine Beziehung, die ist:

zwischen Auswertungen, die von einem einzigen ausgeführt werden Faden und induziert a strenge Teilbestellung1

Formal bedeutet es zwei beliebige Bewertungen(Siehe unten)  Aund B, ob A ist vorher sequenziert  B, dann die Ausführung von A  soll vorangehen die Ausführung von B. Ob A wird vorher nicht sequenziert Bund B wird vorher nicht sequenziert A, dann Aund B sind nicht sequenziert  2.

Bewertungen Aund B sind unbestimmt sequenziert wenn entweder A ist vorher sequenziert worden B oder B ist vorher sequenziert worden A, aber es ist nicht spezifiziert was3.

[ANMERKUNGEN]
  1: Eine strikte Teilbestellung ist a binäre Beziehung  "<" über einen Satz P welches ist asymmetric, und transitivefür alle a, b, und c im P, wir haben das:
 
  ........(ich). wenn a <b then ¬ (b <a) (asymmetry);
  ........ (ii). wenn a <b und b <c dann a <c (transitivity).
  2: Die Ausführung von unsequenced Auswertungen kann Überlappung.
  3 : Unbestimmt sequenzierte Auswertungen kann nicht Überlappung, aber beide könnten 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 Bestimmung der Identität eines Objekts für glvalue Auswertung und Abrufen eines zuvor für ein Objekt zugewiesenen Wertes Prvalue Bewertung) und

  • Einleitung von Nebenwirkungen.

Jetzt (§1.9 / 14) sagt:

Jede Wertberechnung und Nebenwirkung, die einem Vollausdruck zugeordnet ist, ist vorher sequenziert jede Wertberechnung und Nebenwirkung, die mit der nächster auszudrückender Ausdruck.

  • Triviales Beispiel:

    int x; x = 10; ++x;

    Wertberechnung und Nebeneffekt verbunden mit ++x ist nach der Wertberechnung und dem Nebeneffekt von x = 10; 


Also muss es eine Beziehung zwischen Undefined Behavior und den oben genannten Dingen geben, oder?

Ja! Recht.

In (§1.9 / 15) wurde erwähnt, dass

Sofern nicht anders angegeben, sind Bewertungen von Operanden einzelner Operatoren und von Teilausdrücken einzelner Ausdrücke nicht sequenziert4.

Beispielsweise :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Auswertung von Operanden von + Operator sind relativ zueinander nicht sequenziert.
  2. Auswertung von Operanden von <<und >> Operatoren sind relativ zueinander nicht sequenziert.

 4: In einem Ausdruck, der während der Ausführung mehr als einmal ausgewertet wird eines Programms, nicht sequenziertund unbestimmt sequenziert Bewertungen ihrer Unterausdrücke müssen nicht in verschiedenen Auswertungen konsistent durchgeführt werden.

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

Das bedeutet in x + y die Wertberechnung von xund y werden vor der Wertberechnung von (x + y).

Wichtiger

(§1.9 / 15) Wenn eine Nebenwirkung auf ein Skalarobjekt relativ zu beiden nicht sequenziert ist

(ein) ein weiterer Nebeneffekt auf das gleiche skalare Objekt 

oder

(b) eine Wertberechnung unter Verwendung des Werts des gleichen Skalarobjekts.

das Verhalten ist nicht definiert.

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)

Wenn eine Funktion aufgerufen wird (unabhängig davon, ob die Funktion inline ist oder nicht), wird jede mit einem Argumentausdruck verknüpfte Wertberechnung und Nebenwirkung oder der Postfixausdruck, der die aufgerufene Funktion bezeichnet, vor der Ausführung jedes Ausdrucks oder jeder Anweisung im Hauptteil des Befehls sequenziert aufgerufene Funktion. [Hinweis:  Wertberechnungen und mit verschiedenen Argumentausdrücken verknüpfte Nebenwirkungen sind nicht sequenziell. - Endnote]

Ausdrücke (5), (7)und (8) Rufen Sie kein undefiniertes Verhalten auf. Lesen Sie die folgenden Antworten für eine detailliertere Erklärung.


Abschließende Anmerkung :

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


257



C ++ 17 (N4659) enthält einen Vorschlag Bewertungsausführungsreihenfolge für Idiomatic C ++ verfeinern welches eine strengere Reihenfolge der Ausdruckbewertung definiert.

Insbesondere die folgender Satz wurde hinzugefügt:

8.18 Zuweisungs- und Verbundzuweisungsoperatoren:
....

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

Es macht mehrere Fälle von zuvor undefiniertem Verhalten gültig, einschließlich der in Frage stehenden:

a[++i] = i;

Mehrere 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

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


13



Ich vermute, es gibt einen grundlegenden Grund für die Veränderung, es ist nicht nur kosmetisch, die alte Interpretation klarer zu machen: dieser Grund ist Nebenläufigkeit. Nicht spezifizierte Reihenfolge der Ausarbeitung ist lediglich die Auswahl einer von mehreren möglichen seriellen Anordnungen, dies ist ganz anders als vor und nach den Bestellungen, weil, wenn es keine spezifizierte Reihenfolge gibt, eine gleichzeitige Bewertung möglich ist: nicht so mit den alten Regeln. Zum Beispiel in:

f (a,b)

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


11