Ich habe über Verstöße gegen die Reihenfolge der Bewertungen gelesen und sie geben ein Beispiel, das mich verwirrt.
1) Wenn eine Nebenwirkung auf ein Skalarobjekt relativ zu einer anderen Nebenwirkung auf dasselbe Skalarobjekt nicht sequenziert wird, ist das Verhalten undefiniert.
// snip f(i = -1, i = -1); // undefined behavior
In diesem Zusammenhang i
handelt es sich um ein skalares Objekt , was anscheinend bedeutet
Arithmetische Typen (3.9.1), Aufzählungstypen, Zeigertypen, Zeiger auf Elementtypen (3.9.2), std :: nullptr_t und cv-qualifizierte Versionen dieser Typen (3.9.3) werden gemeinsam als Skalartypen bezeichnet.
Ich sehe nicht, wie mehrdeutig die Aussage in diesem Fall ist. Es scheint mir, dass unabhängig davon, ob das erste oder zweite Argument zuerst ausgewertet wird, als i
endet -1
und beide Argumente auch sind -1
.
Kann jemand bitte klarstellen?
AKTUALISIEREN
Ich schätze die ganze Diskussion sehr. Bisher gefällt mir die Antwort von @ harmic sehr gut, da sie die Fallstricke und Feinheiten der Definition dieser Aussage aufdeckt, obwohl sie auf den ersten Blick so einfach aussieht. @ acheong87 weist auf einige Probleme hin, die bei der Verwendung von Referenzen auftreten, aber ich denke, dass dies orthogonal zum Aspekt der nicht sequenzierten Nebenwirkungen dieser Frage ist.
ZUSAMMENFASSUNG
Da diese Frage eine Menge Aufmerksamkeit erhielt, werde ich die wichtigsten Punkte / Antworten zusammenfassen. Lassen Sie mich zunächst einen kleinen Exkurs machen, um darauf hinzuweisen, dass "warum" eng verwandte, aber subtil unterschiedliche Bedeutungen haben kann, nämlich "aus welchem Grund ", "aus welchem Grund " und "zu welchem Zweck ". Ich werde die Antworten gruppieren, nach welcher dieser Bedeutungen des "Warum" sie angesprochen haben.
aus welchem Grund
Die Hauptantwort hier kommt von Paul Draper , wobei Martin J eine ähnliche, aber nicht so umfassende Antwort beisteuert. Paul Drapers Antwort läuft darauf hinaus
Es ist ein undefiniertes Verhalten, da nicht definiert ist, wie das Verhalten ist.
Die Antwort ist insgesamt sehr gut in Bezug auf die Erklärung, was der C ++ - Standard sagt. Es werden auch einige verwandte Fälle von UB wie f(++i, ++i);
und angesprochen f(i=1, i=-1);
. Im ersten der verwandten Fälle ist nicht klar, ob das erste Argument sein sollte i+1
und das zweite i+2
oder umgekehrt; im zweiten ist nicht klar, ob i
nach dem Funktionsaufruf 1 oder -1 sein soll. Beide Fälle sind UB, da sie unter die folgende Regel fallen:
Wenn eine Nebenwirkung auf ein Skalarobjekt relativ zu einer anderen Nebenwirkung auf dasselbe Skalarobjekt nicht sequenziert wird, ist das Verhalten undefiniert.
Daher f(i=-1, i=-1)
ist auch UB, da es unter die gleiche Regel fällt, obwohl die Absicht des Programmierers (IMHO) offensichtlich und eindeutig ist.
Paul Draper macht dies auch in seiner Schlussfolgerung deutlich
Könnte es ein definiertes Verhalten gewesen sein? Ja. Wurde es definiert? Nein.
was uns zu der Frage bringt: "Aus welchem Grund / Zweck wurde f(i=-1, i=-1)
als undefiniertes Verhalten zurückgelassen?"
aus welchem Grund / Zweck
Obwohl der C ++ - Standard einige (möglicherweise nachlässige) Versehen enthält, sind viele Auslassungen gut begründet und dienen einem bestimmten Zweck. Obwohl mir bewusst ist, dass der Zweck oft entweder "die Arbeit des Compiler-Writers erleichtern" oder "schnellerer Code" ist, war ich hauptsächlich daran interessiert zu wissen, ob es einen guten Grund gibt, f(i=-1, i=-1)
als UB zu gehen.
Harmic und Supercat liefern die Hauptantworten, die einen Grund für die UB liefern . Harmic weist darauf hin, dass ein optimierender Compiler die scheinbar atomaren Zuweisungsoperationen in mehrere Maschinenbefehle aufteilen und diese Befehle für eine optimale Geschwindigkeit weiter verschachteln könnte. Dies könnte zu einigen sehr überraschenden Ergebnissen führen: i
endet in seinem Szenario als -2! Harmic zeigt also, wie das mehrmalige Zuweisen desselben Werts zu einer Variablen negative Auswirkungen haben kann, wenn die Operationen nicht sequenziert werden.
supercat bietet eine verwandte Darstellung der Fallstricke des Versuchs, das f(i=-1, i=-1)
zu tun, wie es aussieht. Er weist darauf hin, dass es bei einigen Architekturen strenge Einschränkungen für mehrere gleichzeitige Schreibvorgänge auf dieselbe Speicheradresse gibt. Ein Compiler könnte es schwer haben, dies zu verstehen, wenn wir uns mit etwas weniger Trivialem befassen als f(i=-1, i=-1)
.
Davidf bietet auch ein Beispiel für Interleaving-Anweisungen, die denen von Harmic sehr ähnlich sind.
Obwohl jedes der Beispiele von Harmic, Supercat und Davidf etwas erfunden ist, liefern sie zusammengenommen immer noch einen konkreten Grund, warum f(i=-1, i=-1)
undefiniertes Verhalten sein sollte.
Ich habe die Antwort von harmic akzeptiert, weil sie alle Bedeutungen des Warum am besten angesprochen hat, obwohl Paul Drapers Antwort den Teil "Aus welchem Grund" besser angesprochen hat.
Andere Antwort
JohnB weist darauf hin, dass wir auch Probleme bekommen können , wenn wir überladene Zuweisungsoperatoren (anstelle von einfachen Skalaren) berücksichtigen.
f(i-1, i = -1)
oder etwas Ähnliches.
std::nullptr_t
und cv-qualifizierte Versionen dieser Arten (3.9.3) werden zusammenfassend als skalare Typen . ""