シーケンスポイントと部分順序
-
19-09-2019 - |
質問
数日前に議論がありました ここ 表現かどうかについて
i = ++ i +1
UB(未定義の動作)を呼び出すかどうか。
最後に、「I」の値が2つのシーケンスポイント間で複数回変更されているため、UBを呼び出すという結論がなされました。
私は議論に関与していました ヨハネス・シャウブ 同じスレッドで。彼によると
i =(i、i ++、i)+1 ------(1) / * ubも呼び出します * /
(1)以前のサブ発現の副作用がコンマオペレーターによってクリアされているため、「IとI ++の間、およびI ++とIの間では、UBを呼び起こさない」と言いました。
それから彼は次の説明をしました:
「はい、I ++がその前にすべての副作用を完了した後のシーケンスポイントは、I ++の副作用と重複する割り当て副作用を止めるものは何もありません。根本的な問題は、割り当ての副作用が割り当ての両方のオペランドの評価の後または前または前に発生するように指定されていないため、シーケンスポイントはこれを保護することに関して何もできないことです。 I ++の後と前にシーケンスポイントがあります。すべての副作用がiに関してシーケンスされることを意味しません.
また、単なるシーケンスポイントは何も意味しないことに注意してください。評価の順序はコードの形式によって決定されません。セマンティックルールによって決定されています。この場合、それらのオペランドの両方のオペランドの両方を評価することに関して、割り当て副作用がいつ発生するかを示すセマンティックルールはありません」。
「太字」で書かれた声明は私を混乱させました。私の知る限りでは:
「シーケンスポイントと呼ばれる実行シーケンスの特定の指定されたポイントでは、以前の評価のすべての副作用が完全であり、その後の評価の副作用は行われません。」
また、コンマオペレーターは実行順序を指定しているため、評価の順序が指定されていれば、最後のi.he(johannes)に到達すると、i ++の副作用がキャンセルされています(ただし、コンマオペレーターの場合、よく指定されています。 )。
ですから、(1)UBを呼び出すかどうかを知りたいだけです。誰かが別の有効な説明をすることができますか?
ありがとう!
解決
C標準は、割り当て演算子(C90 6.3.16またはC99 6.5.16割り当て演算子)について次のように述べています。
左オペランドの保存値を更新する副作用は、前と次のシーケンスポイントの間に発生するものとします。
声明の中で私には次のように思えます:
i=(i,i++,i)+1;
割り当てオペレーターのシーケンスポイント「前」は2番目のコンマオペレーターであり、「次の」シーケンスポイントは式の終わりになります。ですから、この表現は未定義の動作を呼び起こさないと思います。
しかし、この表現:
*(some_ptr + i) = (i,i++,i)+1;
割り当てオペレーターの2つのオペランドの評価の順序が未定義であるため、未定義の動作があります。この場合、課題オペレーターの副作用が発生する場合、問題はあなたがの値がわからないことです。左のハンドルで使用したオペランドでは、右側の前後に評価されます。この評価の問題の順序は、最初の例では発生しません。その式ではの値が i
実際には左側では使用されていません - 課題オペレーターが興味を持っているのは、の「lvalue-ness」だけです i
.
しかし、私はこれがすべて十分に大ざっぱだと思います(そして、関係するニュアンスの私の理解は十分に大ざっぱです)。
他のヒント
次の表現は間違いなく未定義の行動を持っていると思います。
i + ((i, i++, i) + 1)
その理由は、コンマオペレーターが括弧内のサブエクスペッション間のシーケンスポイントを指定しますが、そのシーケンスで左手オペランドの評価がどこにあるかを指定していないためです。 +
発生します。 1つの可能性は、周囲のシーケンスポイントの間にあります i++
そして、これは5/4 ASに違反します i
2つのシーケンスポイントの間に書かれていますが、保存する値を決定するだけでなく、最初のオペランドの値を決定するためだけでなく、同じシーケンスポイントの間で2回読み取られます。 +
オペレーター。
これには未定義の動作もあります。
i += (i, i++, i) + 1;
今、私はこの声明についてよくわかりません。
i = (i, i++, i) + 1;
同じプリンシパルが適用されますが、 i
変更可能なlvalueとして「評価」する必要があり、いつでも実行できますが、私はそのことを確信していません 価値 今までです 読んだ この一部として。 (または、式がUBを引き起こすために違反する別の制限はありますか?)
サブ発現 (i, i++, i)
保存される値を決定する一環として発生し、そのサブエクスペッションには、値のストレージの後にシーケンスポイントが含まれています i
. 。これが副作用を必要としない方法はわかりません i++
保存される値を決定する前に完了するため、したがって、割り当て副作用が発生する可能性のある最も早いポイントが発生する可能性があります。
この四肢の後 i
の値はせいぜい1回読まれ、保存される値を決定するためだけに読み取られます i
, 、したがって、この最後の部分は大丈夫です。
i=(i,i++,i)+1 ------ (1) /* invokes UB as well */
未定義の動作は呼び出されません。の副作用 i++
次のシーケンスポイントの評価の前に行われます。これは、それに続くコンマで示され、割り当ての前にも示されます。
しかし、素敵な言語の数独でした。 :-)
編集:もっと手の込んだ説明があります ここ.
私は最初はヨハネス(LITB)の声明について混乱していましたが、彼は次のように述べました。
i = (i, ++i, i) +1
<ヨハネス>
<a>が割り当てであり、増分です。:s:
シーケンスポイントであり、副作用はシーケンスポイント間で次のようにシーケンスできます。(i :s: i++< a ><n> :s: i) + 1
. 。スカラーの値i
ここでは、最初のシーケンスポイントと2番目のシーケンスポイントの間で2回変更されました。割り当てと増分が発生する順序は特定されていません。それらの間にはシーケンスポイントがないため、互いにアトミックさえありません。これは異なります
(i++, i++)
, 、2つのサブ発現の評価順序は左から右であり、それらの間のシーケンスポイントでは、以前の評価の増分が完了し、次の増分はまだ行われていません。これは、の価値の変更がないことを強制しますi
2つのシーケンスポイントの間で、それが作成されます(i++, i++)
有効
< /johannes>
これにより、LITBが言及したシーケンスはC99に従って無効であると思いました。
6.5.16.1(2)単純な割り当て(=)では、適切なオペランドの値は割り当て式のタイプに変換され、左オペランドで指定されたオブジェクトに保存されている値を置き換えます。
つまり、適切なオペランドの値は、割り当て副作用の前に知る必要があります(左オペランドに対応するオブジェクトに保存されている値の変更)
6.5.17(2)コンマオペレーターの左オペランドは、ボイド式として評価されます。評価後、シーケンスポイントがあります。その後、適切なオペランドが評価されます。結果にはその種類と価値があります。
つまり、コンマ操作の右端のオペランドは、コンマの表現の価値とタイプ(および私の例には適切なオペランドの値)を知るために評価する必要があります。
したがって、この場合、割り当て副作用の「以前のシーケンスポイント」は、事実上、最適なコンマ操作になります。ヨハネスが言及した可能なシーケンスは無効です。
私が間違っている場合は私を修正してください。