operator - c++ order of evaluation if statement




Nicht definierte Verhaltens- und Sequenzpunkte (3)

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 Stack Overflow . 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.)


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 .


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.


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

Voraussetzungen : Grundkenntnisse in Beziehungen (Mathematik).

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

Ja! Das ist sehr wahr.

Sequenzpunkte wurden in C ++ 11 durch sequenzielle vor und sequenzielle (und nicht sequenzielle und indeterminiert sequenzierte ) relations .

Was genau ist das 'Sequenced Before' Ding?

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

zwischen Auswertungen, die von einem einzigen thread und induziert eine strikte Teilaufordnung 1

Formal bedeutet das, dass zwei Auswertungen gegeben sind (siehe unten) A und B , wenn A vor B sequenziert wird , dann muss die Ausführung von A der Ausführung von B vorausgehen . Wenn A nicht sequenziert wird, bevor B und B nicht vor A sequenziert sind, sind A und B nicht sequenziert 2 .

Die Auswertungen A und B sind unbestimmt sequenziert, wenn entweder A sequenziert wird, bevor B oder B vor A sequenziert wird, aber es ist nicht spezifiziert, welches 3 .

[ANMERKUNGEN]
1: Eine strikte partielle Ordnung ist eine binäre Beziehung "<" über eine Menge P die Asymmetric und Transitive , dh für alle a , b und c in P gilt:
........(ich). wenn a <b then ¬ (b <a) ( asymmetry );
........ (ii). wenn a <b und b <c, dann a <c ( transitivity ).
2: Die Ausführung von unsymmetrischen Auswertungen kann sich überschneiden .
3: Unbestimmt sequenzierte Auswertungen können nicht überlappen , 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:

Jetzt (§1.9 / 14) sagt:

Jede Wertberechnung und jeder Nebeneffekt, der mit einem Voll-Ausdruck verknüpft ist, wird vor jeder Wertberechnung und jedem Nebeneffekt, der mit dem nächsten auszuwertenden Voll-Ausdruck verknüpft ist, sequenziert .

  • Triviales Beispiel:

    int x; x = 10; ++x;

    Die Wertberechnung und der mit ++x verbundene Nebeneffekt wird nach der Wertberechnung und dem Nebeneffekt von x = 10; sequenziert 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 sequentiell 4 .

Beispielsweise :

int main()
{
     int num = 19 ;
     num = (num << 3) + (num >> 3);
} 
  1. Die Auswertung von Operanden des Operators + ist relativ zueinander unse- quenziert.
  2. Die Auswertung von Operanden von << und >> Operatoren ist relativ zueinander nicht sequentiell.

4: In einem Ausdruck, der während der Ausführung eines Programms mehr als einmal ausgewertet wird, müssen nicht sequenzierte und unbestimmt sequenzierte Auswertungen seiner Unterausdrücke in verschiedenen Auswertungen nicht konsistent 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 die Wertberechnung von x und y vor der Wertberechnung von (x + y) sequenziert.

Wichtiger

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

(a) eine weitere Nebenwirkung auf dasselbe Skalarobjekt

oder

(b) eine Wertberechnung unter Verwendung des Wertes 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 ]

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

Letzter Hinweis :

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.





sequence-points