Bevor Sie anfangen, undefiniertes Verhalten zu schreien, wird dies in N4659 (C ++ 17) explizit aufgeführt.
i = i++ + 1; // the value of i is incremented
Noch in N3337 (C ++ 11)
i = i++ + 1; // the behavior is undefined
Was hat sich geändert?
Soweit ich das beurteilen kann, aus [N4659 basic.exec]
Sofern nicht anders angegeben, werden Auswertungen von Operanden einzelner Operatoren und von Unterausdrücken einzelner Ausdrücke nicht sequenziert. [...] Die Wertberechnungen der Operanden eines Operators werden vor der Wertberechnung des Ergebnisses des Operators sequenziert. Wenn ein Nebeneffekt an einem Speicherort nicht in Bezug auf einen anderen Nebeneffekt an demselben Speicherort oder eine Wertberechnung unter Verwendung des Werts eines Objekts an demselben Speicherort sequenziert wird und diese möglicherweise nicht gleichzeitig auftreten, ist das Verhalten undefiniert.
Wobei der Wert unter [N4659 basic.type] definiert ist
Bei trivial kopierbaren Typen ist die Wertedarstellung eine Menge von Bits in der Objektdarstellung, die einen Wert bestimmt , der ein diskretes Element einer implementierungsdefinierten Menge von Werten ist
Sofern nicht anders angegeben, werden Auswertungen von Operanden einzelner Operatoren und von Unterausdrücken einzelner Ausdrücke nicht sequenziert. [...] Die Wertberechnungen der Operanden eines Operators werden vor der Wertberechnung des Ergebnisses des Operators sequenziert. Wenn eine Nebenwirkung auf ein Skalarobjekt in Bezug auf eine andere Nebenwirkung auf dasselbe Skalarobjekt oder eine Wertberechnung unter Verwendung des Werts desselben Skalarobjekts nicht sequenziert wird, ist das Verhalten undefiniert.
Ebenso ist der Wert unter [N3337 basic.type] definiert.
Bei trivial kopierbaren Typen ist die Wertdarstellung eine Menge von Bits in der Objektdarstellung, die einen Wert bestimmt , der ein diskretes Element eines implementierungsdefinierten Satzes von Werten ist.
Sie sind identisch, mit Ausnahme der Erwähnung der Parallelität, die keine Rolle spielt, und bei Verwendung des Speicherorts anstelle des skalaren Objekts , wo
Arithmetische Typen, Aufzählungstypen, Zeigertypen, Zeiger auf Elementtypen
std::nullptr_t
und lebenslaufqualifizierte Versionen dieser Typen werden gemeinsam als Skalartypen bezeichnet.
Was das Beispiel nicht beeinflusst.
Aus [N4659 expr.ass]
Der Zuweisungsoperator (=) und die zusammengesetzten Zuweisungsoperatoren gruppieren sich alle von rechts nach links. Alle benötigen einen modifizierbaren l-Wert als linken Operanden und geben einen l-Wert zurück, der sich auf den linken Operanden bezieht. Das Ergebnis ist in allen Fällen ein Bitfeld, wenn der linke Operand ein Bitfeld ist. In allen Fällen wird die Zuweisung nach der Wertberechnung des rechten und linken Operanden und vor der Wertberechnung des Zuweisungsausdrucks sequenziert. Der rechte Operand wird vor dem linken Operanden sequenziert.
Aus [N3337 expr.ass]
Der Zuweisungsoperator (=) und die zusammengesetzten Zuweisungsoperatoren gruppieren sich alle von rechts nach links. Alle benötigen einen modifizierbaren l-Wert als linken Operanden und geben einen l-Wert zurück, der sich auf den linken Operanden bezieht. Das Ergebnis ist in allen Fällen ein Bitfeld, wenn der linke Operand ein Bitfeld ist. In allen Fällen wird die Zuweisung nach der Wertberechnung des rechten und linken Operanden und vor der Wertberechnung des Zuweisungsausdrucks sequenziert.
Der einzige Unterschied besteht darin, dass der letzte Satz in N3337 fehlt.
Der letzte Satz sollte jedoch keine Bedeutung haben, da der linke Operand i
weder "ein weiterer Nebeneffekt" noch "den Wert desselben skalaren Objekts verwenden" ist, da der id-Ausdruck ein l-Wert ist.
quelle
i = i++ + 1;
.Antworten:
In C ++ 11 wird der Vorgang der "Zuweisung", dh der Nebeneffekt der Änderung der LHS, nach der Wertberechnung des richtigen Operanden sequenziert . Beachten Sie, dass dies eine relativ "schwache" Garantie ist: Sie erzeugt eine Sequenzierung nur in Bezug auf die Wertberechnung der RHS. Es sagt nichts über die Nebenwirkungen aus , die in der RHS vorhanden sein könnten, da das Auftreten von Nebenwirkungen nicht Teil der Wertberechnung ist . Die Anforderungen von C ++ 11 legen keine relative Reihenfolge zwischen dem Zuweisungsvorgang und den Nebenwirkungen der RHS fest. Dies schafft das Potenzial für UB.
Die einzige Hoffnung in diesem Fall sind zusätzliche Garantien, die von bestimmten in RHS verwendeten Betreibern gegeben werden. Wenn die RHS ein Präfix verwendet hätte
++
, hätten Sequenzierungseigenschaften, die für die Präfixform von spezifisch sind,++
in diesem Beispiel den Tag gerettet. Aber Postfix++
ist eine andere Geschichte: Es gibt keine solchen Garantien. In C ++ 11 bleiben die Nebenwirkungen von=
und Postfix++
in diesem Beispiel in Bezug zueinander unsequenziert. Und das ist UB.In C ++ 17 wird der Spezifikation des Zuweisungsoperators ein zusätzlicher Satz hinzugefügt:
In Kombination mit dem oben Gesagten ergibt sich eine sehr starke Garantie. Es sequenziert alles , was in der RHS passiert (einschließlich aller Nebenwirkungen), vor allem , was in der LHS passiert. Da die eigentliche Zuordnung nach LHS (und RHS) sequenziert wird, isoliert diese zusätzliche Sequenzierung den Zuweisungsvorgang vollständig von allen in RHS vorhandenen Nebenwirkungen. Diese stärkere Sequenzierung eliminiert die obige UB.
(Aktualisiert, um die Kommentare von @John Bollinger zu berücksichtigen.)
quelle
x -= y;
so verarbeitet werden ,mov eax,[y] / sub [x],eax
als vielmehrmov eax,[x] / neg eax / add eax,[y] / mov [x],eax
. Daran sehe ich nichts Idiodisches. Wenn man eine Reihenfolge angeben müsste, wäre die effizienteste Reihenfolge wahrscheinlich, alle Berechnungen durchzuführen, die erforderlich sind, um zuerst das Objekt auf der linken Seite zu identifizieren , dann den rechten Operanden und dann den Wert des linken Objekts auszuwerten , aber dafür wäre ein Term erforderlich für den Vorgang des Auflösens der Identität des linken Objekts.x
undy
warenvolatile
, wäre das Nebenwirkungen haben. Ferner würde gelten die gleichen Überlegungenx += f();
, wof()
modifiziertx
.Sie haben den neuen Satz identifiziert
und Sie haben richtig festgestellt, dass die Bewertung des linken Operanden als Wert irrelevant ist. Die zuvor sequenzierte Beziehung wird jedoch als transitive Beziehung angegeben. Der vollständige rechte Operand (einschließlich des Nachinkrements) wird daher auch vor der Zuweisung sequenziert. In C ++ 11 wurde vor der Zuweisung nur die Wertberechnung des richtigen Operanden sequenziert.
quelle
In älteren C ++ - Standards und in C11 endet die Definition des Zuweisungsoperatortextes mit dem Text:
Dies bedeutet, dass Nebenwirkungen in den Operanden nicht sequenziert werden und daher definitiv undefiniertes Verhalten, wenn sie dieselbe Variable verwenden.
Dieser Text wurde in C ++ 11 einfach entfernt, sodass er etwas mehrdeutig war. Ist es UB oder nicht? Dies wurde in C ++ 17 klargestellt, wo Folgendes hinzugefügt wurde:
Als Randnotiz wurde dies in noch älteren Standards sehr deutlich gemacht, Beispiel aus C99:
Grundsätzlich haben sie in C11 / C ++ 11 Fehler gemacht, als sie diesen Text entfernt haben.
quelle
Dies sind weitere Informationen zu den anderen Antworten und ich poste sie, da häufig auch nach dem folgenden Code gefragt wird .
Die Erklärung in den anderen Antworten ist korrekt und gilt auch für den folgenden Code, der jetzt genau definiert ist (und den gespeicherten Wert von nicht ändert
i
):Das
+ 1
ist ein roter Hering und es ist nicht wirklich klar, warum der Standard ihn in ihren Beispielen verwendet hat, obwohl ich mich an Leute erinnere, die vor C ++ 11 auf Mailinglisten argumentierten, dass dies möglicherweise+ 1
einen Unterschied gemacht hat, weil die frühzeitige Wertumwandlung auf der rechten Seite erzwungen wurde. Handseite. Sicherlich gilt nichts davon in C ++ 17 (und wahrscheinlich nie in einer Version von C ++).quelle