web-dev-qa-db-ja.com

i = i ++ + 1になった理由; C ++ 17で合法ですか?

未定義の動作を叫ぶ前に、これは明示的ににリストされています N4659(C++ 17)

  i = i++ + 1;        // the value of i is incremented

まだ N3337(C++ 11)

  i = i++ + 1;        // the behavior is undefined

何が変わった?

収集できるものから、 [N4659 basic.exec]

特に明記されていない限り、個々の演算子のオペランドと個々の式の部分式の評価は順序付けられていません。 [...]演算子のオペランドの値計算は、演算子の結果の値計算の前に順序付けられます。メモリロケーションの副作用が、同じメモリロケーションの別の副作用または同じメモリロケーションのオブジェクトの値を使用した値の計算と比較して順序付けられておらず、潜在的に並行していない場合、動作は未定義です。

value[N4659 basic.type] で定義されています

簡単にコピー可能な型の場合、値の表現は、オブジェクト定義のビットのセットであり、実装定義の値のセットの個別の要素であるvalueを決定します

From [N3337 basic.exec]

特に明記されていない限り、個々の演算子のオペランドと個々の式の部分式の評価は順序付けられていません。 [...]演算子のオペランドの値計算は、演算子の結果の値計算の前に順序付けられます。スカラーオブジェクトの副作用が、同じスカラーオブジェクトの別の副作用または同じスカラーオブジェクトの値を使用した値の計算のいずれかに対して順序付けられていない場合、動作は未定義です。

同様に、値は [N3337 basic.type] で定義されます

簡単にコピー可能な型の場合、値表現は、オブジェクト定義内のビットのセットであり、実装定義の値セットの個別の要素であるvalueを決定します。

それらは重要ではない同時性の言及を除いて同一であり、スカラーオブジェクトの代わりにメモリ位置を使用します。

算術型、列挙型、ポインター型、メンバー型へのポインター、std::nullptr_t、およびこれらの型のcv修飾バージョンは、まとめてスカラー型と呼ばれます。

これは例に影響しません。

From [N4659 expr.ass]

代入演算子(=)と複合代入演算子はすべて、右から左にグループ化します。すべては、左オペランドとして変更可能な左辺値を必要とし、左オペランドを参照する左辺値を返します。左のオペランドがビットフィールドの場合、すべての場合の結果はビットフィールドです。すべての場合において、割り当ては、右オペランドと左オペランドの値計算の後、割り当て式の値計算の前にシーケンスされます。右のオペランドは左のオペランドの前にシーケンスされます。

From [N3337 expr.ass]

代入演算子(=)と複合代入演算子はすべて、右から左にグループ化します。すべては、左オペランドとして変更可能な左辺値を必要とし、左オペランドを参照する左辺値を返します。左のオペランドがビットフィールドの場合、すべての場合の結果はビットフィールドです。すべての場合において、割り当ては、右オペランドと左オペランドの値計算の後、割り当て式の値計算の前にシーケンスされます。

唯一の違いは、N3337に最後の文がないことです。

ただし、左のオペランドi"別の副作用"でも"同じスカラーオブジェクトの値を使用"でもないため、最後の文は重要ではありません。 id-expressionは左辺値であるため。

177
Passer By

C++ 11では、「代入」の動作、つまりLHSを変更する副作用は、右オペランドの値計算の後にシーケンスされます。これは比較的「弱い」保証であることに注意してください。RHSの値の計算との関係でのみシーケンスを生成します。副作用の発生は値の計算の一部ではないため、RHSに存在する可能性のある副作用については何も述べていません。 C++ 11の要件は、割り当ての動作とRHSの副作用の間に相対的な順序付けを確立しません。これがUBの可能性を生み出すものです。

この場合の唯一の希望は、RHSで使用される特定のオペレーターによる追加の保証です。 RHSが接頭辞++を使用した場合、この例では++の接頭辞形式に固有の順序付けプロパティが保存されます。ただし、接尾辞++は別の話です。このような保証はありません。 C++ 11では、=と接尾辞++の副作用は、この例では相互に関係なく順序付けられなくなります。そしてそれがUBです。

C++ 17では、余分な文が代入演算子の仕様に追加されます。

右のオペランドは左のオペランドの前にシーケンスされます。

上記と組み合わせることで、非常に強力な保証が得られます。 everythingは、RHSで発生する(副作用を含む)everythingがLHSで発生する前にシーケンスします。実際の割り当てはシーケンス化されるためafter LHS(およびRHS)であるため、この追加のシーケンス化により、割り当ての動作がRHSに存在する副作用から完全に分離されます。この強力なシーケンスが、上記のUBを排除します。

(@John Bollingerのコメントを考慮して更新されました。)

139
AnT

新しい文を識別しました

右のオペランドは左のオペランドの前に並びます。

また、左辺オペランドを左辺値として評価することは無関係であることを正しく識別しました。ただし、が推移的関係になるように指定される前に順序付けされます。したがって、完全な右オペランド(ポストインクリメントを含む)は、代入前にsequencedになります。 C++ 11では、代入前に、右側のオペランドの値計算のみが順序付けられていました。

33
user743382

古いC++標準およびC11では、代入演算子テキストの定義は次のテキストで終わります。

オペランドの評価は順序付けられていません。

オペランドの副作用が順序付けられていないため、それらが同じ変数を使用している場合の動作は確実に未定義です。

このテキストはC++ 11では単純に削除されたため、多少あいまいになりました。それはUBですか、それともそうではありませんか?これは、C++ 17で次のように追加されています。

右のオペランドは左のオペランドの前に並びます。


ちなみに、より古い規格でさえ、これはすべて非常に明確にされていました、C99からの例:

オペランドの評価順序は指定されていません。代入演算子の結果を変更したり、次のシーケンスポイントの後にアクセスしようとした場合の動作は未定義です。

基本的に、C11/C++ 11では、このテキストを削除したときに混乱しました。

7
Lundin