c++ - Nicht definierte Verhaltens- und Sequenzpunkte




2 Answers

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 : Grundkenntnisse in C ++ Standard

Was sind Sequenzpunkte?

Der Standard sagt

An bestimmten spezifizierten Punkten in der Ausführungssequenz, die als Sequenzpunkte bezeichnet werden , müssen alle Nebenwirkungen früherer Bewertungen vollständig sein, und es dürfen keine Nebenwirkungen nachfolgender Auswertungen auftreten. (§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 aufgrund der Nebenwirkung von ++ Operator geändert.

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

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 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 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 a, b ist ein Komma-Operator; in func(a,a++) , ist kein Komma-Operator, sondern nur ein Trennzeichen zwischen den Argumenten a und a++ . Daher ist das Verhalten in diesem Fall nicht definiert) (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 stattfinden ( §1.9/17 ).

1: Hinweis: Die Auswertung eines Full-Expressions kann die Auswertung von Teilausdrücken beinhalten, die nicht lexikalisch Teil des Full-Expressions sind. 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 als

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

Undefiniertes Verhalten kann auch erwartet werden, wenn dieser Internationale Standard die Beschreibung einer expliziten Definition von Verhalten auslässt.

3: Zulässiges undefiniertes Verhalten reicht von einer vollständigen Ignorierung der Situation mit unvorhersagbaren Ergebnissen über ein Verhalten bei der Übersetzung oder Programmausführung in dokumentierter Weise für die Umgebung (mit oder ohne Ausgabe einer Diagnosemeldung) bis zum Abbruch einer Übersetzung oder Ausführung (mit der Ausgabe einer Diagnosemeldung).

Kurzum, undefiniertes Verhalten bedeutet, dass alles passieren kann, von Dämonen, die aus deiner Nase fliegen, bis deine Freundin schwanger wird.

Was ist die Beziehung zwischen nicht definiertem Verhalten und Sequenzpunkten?

Bevor ich darauf eingehe, müssen Sie die Unterschiede zwischen nicht definiertem Verhalten, nicht spezifiziertem Verhalten und implementiertem definiertem Verhalten kennen .

Sie müssen auch wissen, dass 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 here .

Jetzt sagt der Standard in §5/4

  • 1) Zwischen dem vorherigen und dem nächsten Sequenzpunkt darf ein skalarer Gegenstand seinen gespeicherten Wert höchstens einmal durch die Auswertung eines Ausdrucks verändern.

Was heißt das?

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

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) Außerdem darf nur auf den früheren Wert zugegriffen werden, um den zu speichernden Wert zu bestimmen.

Was heißt das? Das bedeutet, wenn ein Objekt in einen vollständigen Ausdruck geschrieben wird, müssen alle Zugriffe darauf innerhalb desselben Ausdrucks direkt in die Berechnung des zu schreibenden Wertes einbezogen werden .

Zum Beispiel sind in i = i + 1 alle Zugriffe von i (in LHS und in RHS) direkt in die Berechnung des zu schreibenden Wertes involviert . 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, weil einer der Zugriffe von i (der in a[i] ) nichts mit dem Wert zu tun hat, der letztendlich in i gespeichert wird (was in i++ passiert), und deshalb gibt es keinen guten Weg zu definieren-- entweder für unser Verständnis oder den Compiler - ob der Zugriff vor oder nach dem Speichern des inkrementierten Wertes erfolgen soll. Das Verhalten ist also nicht definiert.

Beispiel 3:

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

Folge Antwort here .

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 unbedingt die Folgefrage Undefiniertes Verhalten und neu geladene Sequenzpunkte .

(Hinweis: Dies ist ein Eintrag in die C ++ - FAQ von . Wenn Sie die Idee, eine FAQ in diesem Formular bereitzustellen, kritisieren möchten, dann wäre das Posting auf meta, mit dem all dies begonnen wurde , der richtige Ort dafür Diese Frage wird im C ++ - Chatraum überwacht, wo die FAQ-Idee von Anfang an begann, so dass Ihre Antwort sehr wahrscheinlich von denjenigen gelesen wird, die die Idee hatten.)




Ich vermute, dass es einen grundlegenden Grund für die Veränderung gibt, es ist nicht nur kosmetisch, die alte Interpretation klarer zu machen: dieser Grund ist Nebenläufigkeit. Die 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 Auswertung 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.




Related