何がi = i ++ + 1; C ++ 17で合法?


186

未定義の動作を叫ぶ前に、これは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 basic.type]が定義されている

自明にコピー可能な型の場合、値の表現はオブジェクト表現のビットのセットであり、 あり、実装で定義された一連の値の1つの個別の要素であるvalueます。

から [N3337 basic.exec]

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

同様に、値は [N3337 basic.type]ます

自明にコピー可能な型の場合、値の表現は、オブジェクトで表現された一連のビットであり、実装で定義された一連の値の1つの個別の要素であるvalueを決定します。

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

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

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

[N4659 expr.ass]

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

[N3337 expr.ass]

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

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

左オペランドとして最後の文は、しかし、任意の重要性を持つべきではないiでもない「別の副作用」「同じスカラーオブジェクトの値を使用して、」ID-式は左辺値です。


23
理由は次のとおりです。C++ 17では、右のオペランドが左のオペランドの前にシーケンスされます。C ++ 11では、そのような順序付けはありませんでした。正確には、あなたの質問は何ですか?
Robᵩ

4
@Robᵩ最後の文を参照してください。
2017年

7
この変更の動機へのリンクはありますか?のようなコードに直面したときに、静的アナライザーが「それをしたくない」と言えるようにしたいと思いますi = i++ + 1;

7
@NeilButterworth、それはペーパーp0145r3.pdfからのものです:「慣用的なC ++の式評価順序の精緻化」。
xaizek 2017

9
@NeilButterworth、セクション番号2は、これは直観に反するものであり、専門家でさえすべての場合において正しいことを行うことができないと述べています。それがほとんどすべての動機です。
xaizek 2017

回答:


144

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

この場合の唯一の希望は、RHSで使用される特定のオペレーターによって行われる追加の保証です。RHSがprefixを使用した場合、この例++++は、の接頭辞形式に固有の順序付けプロパティがその日を節約します。しかし、postfix ++は別の話です:そのような保証はしません。C ++ 11では、この例では=、およびpostfixの副作用は、++相互に関連してシーケンスされていません。そしてそれがUBです。

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

右のオペランドは左のオペランドの前に順序付けられます。

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

(@John Bollingerのコメントを考慮するように更新されました。)


3
その抜粋で「左側のオペランド」がカバーする効果に「割り当ての実際の行為」を含めることは本当に正しいのですか?標準には、実際の割り当ての順序付けについて別の言語があります。あなたが提示した抜粋は、左側と右側の部分式のシーケンスに範囲が限定されていると解釈しています。これは、そのセクションの他の部分と組み合わせて、十分にサポートするのに十分ではないようです。 OPのステートメントの定義。
John Bollinger、2017

11
修正:実際の割り当ては、左のオペランドの値の計算後もシーケンスされ、左のオペランドの評価は、右のオペランドの(完全な)評価後にシーケンスされるため、その変更は、OPの明確な定義をサポートするのに十分ですについて尋ねた。そのため、私は詳細を誤解しているだけですが、コードごとに異なる影響を与える可能性があるため、これらは重要です。
ジョンボリンジャー

3
@JohnBollinger:標準の作成者が、簡単なコード生成の効率さえ損なう変更を加え、歴史的に必要ではなかったのに、不在がはるかに大きな問題である他の動作を定義することに不満を感じている効率に意味のある障害をもたらすことはめったにありません。
スーパーキャット2017

1
@Kaz:複合代入の場合、右側の値の後に左側の値の評価x -= y;を実行すると、mov eax,[y] / sub [x],eaxではなくのようなものが処理されmov eax,[x] / neg eax / add eax,[y] / mov [x],eaxます。私はそれについて馬鹿げたことを何も見ていません。順序を指定する必要がある場合、最も効率的な順序は、左側のオブジェクトを特定するために必要なすべての計算を最初に実行し、次に右側のオペランドを評価し、次に左側のオブジェクトのを評価することですが、これには項が必要です。左のオブジェクトのidを解決する行為のため。
スーパーキャット

1
@Kazは:場合xyしたvolatile、それは副作用を持っているでしょう。さらに、同じ考慮事項がを変更するx += f();にも適用されます。f()x
スーパーキャット

33

新しい文章を特定しました

右のオペランドは左のオペランドの前に順序付けられます。

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


7

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

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

オペランドの副作用はシーケンスされていないため、同じ変数を使用した場合の動作は確実に定義されていません。

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

右のオペランドは左のオペランドの前に順序付けられます。


補足として、さらに古い標準では、これはすべてC99の例で非常に明確にされていました。

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

基本的に、C11 / C ++ 11では、このテキストを削除するとめちゃくちゃになりました。


1

これは他の回答の詳細情報です以下のコードもよく聞かれるので、私はそれを投稿しています

他の回答の説明は正しく、次のコードにも当てはまります。これは明確に定義されている(そしての格納値を変更しないi):

i = i++;

これ+ 1は真っ赤なニシンであり、標準がその例でなぜそれを使用したのかははっきりしていませんが、C ++ 11より前のメーリングリストで+ 1、初期の左辺値変換を強制することで違いが生じたと主張している人々を思い出しますハンドサイド。確かに、C ++ 17にはそれが適用されません(おそらく、C ++のどのバージョンにも適用されないでしょう)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.