1. ホーム
  2. c++

[解決済み] C++17でi = i++ + 1;が合法になったのはなぜですか?

2022-04-20 04:34:36

質問

未定義の動作を叫び始める前に、これは 明示的に に記載されています。 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】です。]

特記されている場合を除き、個々の演算子のオペランドと個々の式の部分式の評価は非シーケンスである。[演算子のオペランドの値計算は、演算子の結果の値計算の前に順序付けされる。メモリ位置の副作用が、同じメモリ位置の別の副作用または同じメモリ位置の任意のオブジェクトの値を使用する値計算のいずれかに対してシーケンスされておらず、それらが潜在的に同時でない場合、その動作は未定義である。

ここで で定義されています。 [N4659 基本.type]を参照してください。

トリビアルコピー可能な型では、値表現はオブジェクト表現におけるビットのセットであり、そのビットが これは、実装で定義された値の集合の1つの離散的な要素である。

から [N3337 basic.exec]を参照してください。

特記されている場合を除き、個々の演算子のオペランドと個々の式の部分式の評価は非シーケンスである。[演算子のオペランドの値計算は,演算子の結果の値計算の前に連続する。スカラオブジェクトの副作用が、同じスカラオブジェクトの別の副作用または同じスカラオブジェクトの値を使用する値計算のいずれかに対してシーケンスされていない場合、その動作は未定義である。

同様に、valueは [N3337 基本.タイプ]。

トリビアルコピー可能な型では、値表現はオブジェクト表現におけるビットのセットであり、そのビットが これは、実装で定義された値の集合の1つの離散的な要素である。

並行処理について言及している以外は同一であり、重要ではありません。 メモリロケーション の代わりに スカラーオブジェクト ここで

算術型、列挙型、ポインタ型、メンバ型へのポインタ。 std::nullptr_t そして,これらの型の cv 適合バージョンを総称してスカラ型と呼びます.

というのは、この例には影響しない。

から [N4659 expr.ass】です。]

代入演算子(=)と複合代入演算子は、すべて右から左へのグループ化です。すべて左オペランドとして変更可能な lvalue を必要とし、左オペランドを参照する lvalue を返します。左オペランドがビットフィールドの場合、結果はすべてビットフィールドになります。すべての場合において,代入は,右及び左オペランドの値計算の後,代入式の値計算の前に,順次行われる。右オペランドは、左オペランドより前に配列される。

から [N3337 expr.ass]を参照してください。

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

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

しかし、最後の文は左オペランドとして重要ではないはずです。 i もう一つの副作用。 また "同じスカラーオブジェクトの値を使用する" として id-式 はlvalueである。

どのように解決するのですか?

C++11では、代入の行為、つまりLHSを変更する副作用は、quot;assign"の後に続いています。 値の計算 右オペランドの これは比較的弱い保証であることに注意してください。 値計算 を使用する。については何も言っていない。 サイドエフェクト の一部ではないので、RHSに存在する可能性のある副作用の発生は 値計算 . C++11の要件では、代入行為とRHSのあらゆる副作用の間に相対的な順序が確立されていません。これが、UB の可能性を生み出しているのです。

この場合、唯一の希望は、RHSで使用される特定の演算子による追加保証です。もしRHSが接頭辞 ++ の接頭辞に特有のシーケンス特性がある。 ++ が、この例では救われたことでしょう。しかし、後置修飾子 ++ は、そのような保証はありません。C++11では、副作用として = とポストフィックス ++ は、この例では互いに関連することなく終了します。そして、それがUBです。

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

右オペランドは左オペランドより前に配列される。

上記と組み合わせることで、非常に強力な保証となる。これは すべて の前に、RHS で起こること (副作用を含む) をすべて調べます。 すべて LHSで発生する 実際の代入は順番に行われるので LHS (およびRHS) では、この特別なシーケンスによって、RHSに存在するあらゆる副作用から代入行為が完全に隔離されます。この強力な順序付けが、上記のUBを解消するのです。

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