web-dev-qa-db-de.com

Wodurch wurde i = i ++ + 1; legal in C ++ 17?

Bevor Sie undefiniertes Verhalten schreien, wird dies explizit in N4659 (C++ 17) 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, sind 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 an einem Speicherort nicht im Verhältnis zu einer anderen Nebenwirkung an demselben Speicherort oder einer Wertberechnung unter Verwendung des Werts eines Objekts an demselben Speicherort auftritt und diese möglicherweise nicht gleichzeitig auftreten, ist das Verhalten undefiniert.

Wobei Wert definiert ist unter [N4659 Grundtyp]

Bei trivial kopierbaren Typen ist die Wertdarstellung eine Menge von Bits in der Objektdarstellung, die ein Wert bestimmt, das ein diskretes Element einer durch die Implementierung definierten Menge von Werten ist

From [N3337 basic.exec]

Sofern nicht anders angegeben, sind 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 nicht im Verhältnis zu einer anderen Nebenwirkung auf dasselbe Skalarobjekt oder einer Wertberechnung unter Verwendung des Werts desselben Skalarobjekts auftritt, ist das Verhalten undefiniert.

Ebenso wird der Wert bei [N3337 Grundtyp] definiert

Bei trivial kopierbaren Typen ist die Wertdarstellung eine Menge von Bits in der Objektdarstellung, die ein Wert bestimmt, das ein diskretes Element einer durch die Implementierung definierten Menge von Werten ist.

Sie sind bis auf die Erwähnung der Nebenläufigkeit, die keine Rolle spielt, und mit der Verwendung von Speicherort anstelle von Skalarobjekt, wo

Arithmetische Typen, Aufzählungstypen, Zeigertypen, Zeiger auf Elementtypen, std::nullptr_t und cv-qualifizierte Versionen dieser Typen werden gemeinsam als skalare Typen bezeichnet.

Was das Beispiel nicht beeinflusst.

From [N4659 expr.ass]

Der Zuweisungsoperator (=) und die zusammengesetzten Zuweisungsoperatoren gruppieren sich alle von rechts nach links. Alle benötigen einen veränderbaren Wert als linken Operanden und geben einen 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.

From [N3337 expr.ass]

Der Zuweisungsoperator (=) und die zusammengesetzten Zuweisungsoperatoren gruppieren sich alle von rechts nach links. Alle benötigen einen veränderbaren Wert als linken Operanden und geben einen 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 ist, dass der letzte Satz in N3337 fehlt.

Der letzte Satz sollte jedoch keine Bedeutung haben, da der linke Operand i weder "eine andere Nebenwirkung" noch "mit dem Wert desselben Skalars ist Objekt " als id-Ausdruck ist ein lWert.

177
Passer By

In C++ 11 wird der Vorgang der "Zuweisung", d. H. Der Nebeneffekt der Änderung der LHS, nach der Wertberechnung des rechten Operanden sequenziert. Beachten Sie, dass dies eine relativ "schwache" Garantie ist: Es wird nur eine Sequenzierung in Bezug auf Wertberechnung der RHS durchgeführt. Es sagt nichts über die Nebenwirkungen aus, die in der RHS vorhanden sein könnten, da das Auftreten von Nebenwirkungen nicht Teil von Wertberechnung ist. Die Anforderungen von C++ 11 legen keine relative Reihenfolge zwischen dem Zuteilungsvorgang und irgendwelchen Nebenwirkungen der RHS fest. Dies schafft das Potenzial für UB.

Die einzige Hoffnung in diesem Fall sind irgendwelche zusätzlichen Garantien, die von bestimmten in RHS verwendeten Betreibern gegeben werden. Wenn die RHS ein Präfix ++ Verwendet hätte, hätte die Sequenzierung von Eigenschaften, die für die Präfixform von ++ Spezifisch sind, den Tag in diesem Beispiel gespeichert. Aber Postfix ++ Ist eine andere Geschichte: Es gibt keine solchen Garantien. In C++ 11 werden die Nebenwirkungen von = Und Postfix ++ In diesem Beispiel nicht aufeinander bezogen. Und das ist UB.

In C++ 17 wird ein zusätzlicher Satz zur Angabe des Zuweisungsoperators hinzugefügt:

Der rechte Operand wird vor dem linken Operanden sequenziert.

In Kombination mit dem oben genannten ergibt sich eine sehr starke Garantie. Es folgt alles, das in der RHS passiert (einschließlich aller Nebenwirkungen), bevor alles, das in der LHS passiert. Da die tatsächliche Zuweisung in der Reihenfolge nach LHS (und RHS) erfolgt, isoliert diese zusätzliche Reihenfolge den Vorgang der Zuweisung vollständig von allen in RHS vorhandenen Nebenwirkungen. Diese stärkere Sequenzierung eliminiert das obige UB.

(Aktualisiert, um die Kommentare von @John Bollinger zu berücksichtigen.)

139
AnT

Sie haben den neuen Satz identifiziert

Der rechte Operand wird vor dem linken Operanden sequenziert.

und Sie haben richtig erkannt, dass die Auswertung des linken Operanden als lWert irrelevant ist. zuvor sequenziert wird jedoch als transitives Verhältnis 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.

33
user743382

In älteren C++ - Standards und in C11 endet die Definition des Zuweisungsoperatortexts mit dem folgenden Text:

Die Auswertungen der Operanden sind nicht sequenziert.

Dies bedeutet, dass Nebenwirkungen in den Operanden nicht sequenziert werden und daher definitiv undefiniert sind, wenn sie dieselbe Variable verwenden.

Dieser Text wurde in C++ 11 einfach entfernt, sodass er nicht eindeutig ist. Ist es UB oder nicht? Dies wurde in C++ 17 klargestellt, wo Folgendes hinzugefügt wurde:

Der rechte Operand wird vor dem linken Operanden sequenziert.


Als Randnotiz, in noch älteren Standards, wurde dies alles sehr deutlich gemacht, Beispiel aus C99:

Die Reihenfolge der Auswertung der Operanden ist nicht festgelegt. Wenn versucht wird, das Ergebnis eines Zuweisungsoperators zu ändern oder nach dem nächsten Sequenzpunkt darauf zuzugreifen, ist das Verhalten undefiniert.

Grundsätzlich haben sie in C11/C++ 11 Fehler gemacht, als sie diesen Text entfernt haben.

7
Lundin