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, macht Sie das erneute Lesen nicht verrückt.
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 es dürfen keine Nebenwirkungen nachfolgender Bewertungen aufgetreten sein. (§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 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 ++
Bedieners geändert .
So weit, ist es gut. Weiter zu Sequenzpunkten. Eine vom Autor von comp.lang.c angegebene alternative Definition von Seq-Punkten Steve Summit
:
Der Sequenzpunkt ist ein Zeitpunkt, zu 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?
Jene 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 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; in func(a,a++)
,
ist kein Kommaoperator, es ist lediglich ein Trennzeichen zwischen den Argumenten a
und a++
. Daher ist das Verhalten in diesem Fall undefiniert (wenn a
es 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
) erfolgt.
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 davon ausgegangen, 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 integrierten Operatoren, wie in Abschnitt 5 beschrieben. Wenn einer dieser Operatoren in einem gültigen Kontext überladen ist (Abschnitt 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 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 stellt 3 .
Undefiniertes Verhalten kann auch erwartet werden, wenn in dieser Internationalen Norm die Beschreibung einer expliziten Definition des Verhaltens weggelassen wird.
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 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, dass alles passieren kann, von Dämonen, die aus Ihrer Nase fliegen, bis zu Ihrer schwangeren Freundin.
Welche Beziehung besteht zwischen undefiniertem Verhalten und Sequenzpunkten?
Bevor ich darauf eingehe, müssen Sie den Unterschied zwischen undefiniertem Verhalten, nicht spezifiziertem Verhalten und implementierungsdefiniertem Verhalten kennen .
Das müssen Sie 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
.
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 .
Jetzt §5/4
sagt der Standard in
- 1) Zwischen dem vorherigen und dem nächsten Sequenzpunkt muss der gespeicherte Wert eines Skalarobjekts höchstens einmal durch Auswertung eines Ausdrucks geändert werden.
Was bedeutet das?
Informell bedeutet dies, dass zwischen zwei Sequenzpunkten eine Variable nicht mehr als einmal geändert werden darf. In einer Ausdrucksanweisung steht das next sequence point
normalerweise am abschließenden Semikolon und das previous sequence point
am Ende der vorherigen Anweisung. Ein Ausdruck kann auch ein Zwischenprodukt enthalten sequence points
.
Aus dem obigen Satz rufen die folgenden Ausdrücke undefiniertes Verhalten hervor:
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) Auf den vorherigen Wert darf nur zugegriffen werden, um den zu speichernden Wert zu bestimmen.
Was bedeutet das? Wenn ein Objekt innerhalb eines vollständigen Ausdrucks beschrieben wird, müssen alle Zugriffe innerhalb desselben Ausdrucks direkt an der Berechnung des zu schreibenden Werts beteiligt sein .
Zum Beispiel sind bei i = i + 1
allen Zugriffen von i
(in LHS und in RHS) direkt an der Berechnung des zu schreibenden Wertes beteiligt. Also ist es gut.
Diese Regel beschränkt rechtliche Ausdrücke effektiv auf diejenigen, bei denen die Zugriffe nachweislich der Änderung vorausgehen.
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
wird nicht zugelassen, weil einer der Zugriffe von i
(der in a[i]
) nichts mit dem Wert zu tun hat, der in i gespeichert wird (was in in geschieht i++
), und daher gibt es keine gute Möglichkeit zu definieren - weder für unser Verständnis noch für das Compiler - Gibt an, ob der Zugriff vor oder nach dem Speichern des inkrementierten Werts erfolgen soll. Das Verhalten ist also undefiniert.
Beispiel 3:
int x = i + i++ ;// Similar to above
Folgen Sie der Antwort für C ++ 11 hier .
*p++ = 4
ist nicht undefiniertes Verhalten.*p++
wird interpretiert als*(p++)
.p++
gibtp
(eine Kopie) zurück und der Wert wird an der vorherigen Adresse gespeichert. Warum sollte das UB aufrufen? Es ist vollkommen in Ordnung.++i
und der Zuweisung zu kein Sequenzpunkt liegti
. Der zweite Ausdruck ruft UB nicht auf, da der Ausdrucki
den Wert von nicht änderti
. Im zweiten Beispieli++
folgt auf einen Sequenzpunkt (,
), bevor der Zuweisungsoperator aufgerufen wird.Dies ist eine Fortsetzung meiner vorherigen Antwort und enthält C ++ 11-bezogenes 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 die Beziehungen Sequenced Before und Sequenced After (und Unsequenced and Indeterminately Sequenced ) ersetzt .
Was genau ist dieses "Sequenced before" -Ding?
Sequenced Before (§1.9 / 13) ist eine Beziehung, die ist:
zwischen Auswertungen, die von einem einzelnen Thread ausgeführt werden und eine strikte Teilreihenfolge induzieren 1
Formal bedeutet es zwei beliebige Auswertungen gegeben (siehe unten)
A
undB
, wennA
sie vor sequenziertB
, dann der AusführungA
soll vorangehen , die AusführungB
. WennA
vorher nicht sequenziert wurdeB
und vorherB
nicht sequenziert wurdeA
, dannA
undB
sind nicht sequenziert 2 .Auswertungen
A
undB
sind unbestimmt sequenziert, wenn entwederA
vorherB
oderB
vorher sequenziert wirdA
, aber es ist nicht spezifiziert, welche 3 .[Anmerkungen]
1: Eine strenge Teilordnung ist eine binäre Relation
"<"
über eine Menge ,P
das istasymmetric
, undtransitive
, das heißt, für allea
,b
undc
inP
, haben wir , dass:........ (i). wenn a <b dann ¬ (b <a) (
asymmetry
);........ (ii). wenn a <b und b <c, dann a <c (
transitivity
).2: Die Ausführung nicht sequenzierter Auswertungen kann sich überschneiden .
3: Unbestimmt sequenzierte Auswertungen können sich nicht überlappen , können jedoch zuerst ausgeführt werden.
Was bedeutet das Wort "Evaluierung" im Kontext von C ++ 11?
In C ++ 11 umfasst die Bewertung eines Ausdrucks (oder eines Unterausdrucks) im Allgemeinen:
Wertberechnungen (einschließlich Bestimmen der Identität eines Objekts für die Gl- Wert- Bewertung und Abrufen eines Werts, der zuvor einem Objekt für die Wert-Bewertung zugewiesen wurde ) und
Einleitung von Nebenwirkungen .
Jetzt (§1.9 / 14) heißt es:
Triviales Beispiel:
int x;
x = 10;
++x;
Die damit verbundene
++x
Wertberechnung und Nebenwirkung wird nach der Wertberechnung und Nebenwirkung von sequenziertx = 10;
Es muss also eine Beziehung zwischen undefiniertem Verhalten und den oben genannten Dingen geben, oder?
Ja! Recht.
In (§1.9 / 15) wurde erwähnt, dass
Beispielsweise :
+
Operators ist relativ zueinander nicht sequenziert.<<
und>>
Operatoren ist relativ zueinander nicht sequenziert.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 nicht konsistent in verschiedenen Auswertungen durchgeführt werden.
Das heißt bei
x + y
der Wertberechnung vonx
undy
werden vor der Wertberechnung von sequenziert(x + y)
.Wichtiger
Beispiele:
i = i++ * ++i; // Undefined Behaviour
i = ++i + i++; // Undefined Behaviour
i = ++i + ++i; // Undefined Behaviour
i = v[i++]; // Undefined Behaviour
i = v[++i]: // Well-defined Behavior
i = i++ + 1; // Undefined Behaviour
i = ++i + 1; // Well-defined Behaviour
++++i; // Well-defined Behaviour
f(i = -1, i = -1); // Undefined Behaviour (see below)
Ausdrücke
(5)
,(7)
und(8)
nicht undefiniert Verhalten aufrufen. In den folgenden Antworten finden Sie eine ausführlichere Erklärung.Schlussbemerkung :
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.
quelle
f(i = -1, i = 1)
?++i
(der Wert wird vor dem+
Bediener ausgewertet ) nicht sagt, dass seine Nebenwirkung beendet werden muss. Aber in der Tat, weil es einen Verweis auf a zurückgibt,lvalue
deri
selbst ist, MUSS es den Nebeneffekt beendet haben, da die Bewertung abgeschlossen sein muss, daher muss der Wert auf dem neuesten Stand sein. Dies war der verrückte Teil, um tatsächlich zu bekommen.C ++ 17 (
N4659
) enthält einen Vorschlag zur Verfeinerung der Ausdrucksbewertungsreihenfolge für idiomatisches C ++, der eine strengere Reihenfolge der Ausdrucksbewertung definiert.Insbesondere der folgende Satz
zusammen mit der folgenden Klarstellung
Machen Sie mehrere Fälle von zuvor undefiniertem Verhalten gültig, einschließlich des fraglichen:
Einige andere ähnliche Fälle führen jedoch immer noch zu undefiniertem Verhalten.
In
N4140
:Aber in
N4659
Die Verwendung eines C ++ 17-kompatiblen Compilers bedeutet natürlich nicht unbedingt, dass man mit dem Schreiben solcher Ausdrücke beginnen sollte.
quelle
i = i++ + 1;
definierte Verhalten in C ++ 17, glaube ich , auch wenn „Der rechte Operand vor dem linken Operanden sequenziert wird“, aber die Modifikation für „i ++“ und die Nebenwirkung für die Zuordnung ist unsequenced, bitte weitere Informationen geben , um diese zu interpretierenIch vermute, es gibt einen fundamentalen Grund für die Änderung, es ist nicht nur kosmetisch, die alte Interpretation klarer zu machen: Dieser Grund ist Parallelität. Eine nicht spezifizierte Reihenfolge der Ausarbeitung ist lediglich die Auswahl einer von mehreren möglichen Serienreihenfolgen. Dies unterscheidet sich erheblich von der Reihenfolge vor und nach der Bestellung, da eine gleichzeitige Auswertung möglich ist, wenn keine spezifizierte Reihenfolge vorliegt: Nicht so bei den alten Regeln. Zum Beispiel in:
vorher entweder a dann b oder b dann a. Jetzt können a und b mit verschachtelten Anweisungen oder sogar auf verschiedenen Kernen ausgewertet werden.
quelle
In
C99(ISO/IEC 9899:TC3)
dieser Diskussion scheinen bisher die folgenden Aussagen zur Reihenfolge der Bewertung getroffen worden zu sein.quelle