私は最近投稿に出くわしましたcout << c++ << c;の正解は何ですか? の出力かどうか疑問に思っていました
int c = 0;
printf ("%d %d", c++, c);
も未定義ですか??
私は講義で、後置演算子と前置演算子はセミコロンを取得した後にのみ値をインクリメントすることを学びました。したがって、私によると、出力0 0
は正しいです!!!
私は最近投稿に出くわしましたcout << c++ << c;の正解は何ですか? の出力かどうか疑問に思っていました
int c = 0;
printf ("%d %d", c++, c);
も未定義ですか??
私は講義で、後置演算子と前置演算子はセミコロンを取得した後にのみ値をインクリメントすることを学びました。したがって、私によると、出力0 0
は正しいです!!!
私は講義で、接頭辞と接頭辞の演算子がセミコロンを取得した後にのみ値をインクリメントすることを研究しました。
私に野球のバットを持って行って彼の間違いを丁寧に指摘できるように、あなたの講師を私に送ってください。
次のシーケンスポイントの前に発生するという要件を除いて、前置または後置++
のいずれかの副作用--
が指定されていない場合。のような表現で
x = a++ * b
a
評価された直後に更新される場合もあれば、評価されて結果がに割り当てられるまで、またはその間の任意の場所に a++
更新が延期される場合もあります。a++ * b
x
これが、andや他の多くの表現がすべて悪いjujuであるi++ * i++
理由printf("%d %d", c++, c)
ですa[i++] = i
。コンパイラ、最適化設定、周囲のコードなどに基づいて、異なる結果が得られます。言語標準では、動作が明示的に未定義のままであるため、コンパイラは、正しいことが何であれ、「正しいことを行う」義務を負いません。未定義動作の定義は次のとおりです。
3.4.3
1 この国際標準が要件を課していない、移植性のない、または誤ったプログラム構成または誤ったデータの使用時の未定義の動作動作 2注考えられる未定義の動作は、予測できない結果を伴う状況を完全に無視することから、翻訳中またはプログラムの実行中の動作にまで及びます。 (診断メッセージの発行の有無にかかわらず)環境に特徴的な文書化された方法で、(診断メッセージの発行を伴う)翻訳または実行を終了します。 3例未定義動作の例は、整数のオーバーフローでの動作です。
これは意図的な設計上の決定です。これらの操作の順序を指定しないままにする理由は、最適化の目的で評価の順序を再配置する自由を実装に与えることです。ただし、この自由と引き換えに、特定の操作では明確な結果が得られません。
コンパイラーはこれらのケースの検出と診断の発行を自由に試みることができることに注意してください。printf("%d %d", c++, c);
キャッチするのは簡単ですが、これは一般的なケースでは検出するのにバガーになります。それが書かれていたと想像してみてくださいprintf("%d %d", (*p)++, c)
; p
を指す場合c
、動作は未定義です。それ以外の場合は問題ありません。が別の変換単位で割り当てられている場合p
、これが問題であるかどうかをコンパイル時に知る方法はありません。
この概念を理解するのは難しいことではありませんが、C言語の最も一貫して誤解されている(そして誤って教えられている)側面の1つです。これが、JavaおよびC#言語仕様がすべてに対して特定の評価順序を強制する理由であることは間違いありません(すべてのオペランドは左から右に評価され、すべての副作用がすぐに適用されます)。
私は講義で、後置演算子と前置演算子はセミコロンを取得した後にのみ値をインクリメントすることを学びました
これは、規格で説明されている方法ではありません。シーケンスポイントは、コードの前の部分で発生した可能性のある副作用が評価されたコード内のポイントです。関数への引数間のコンマはシーケンス ポイントではないため、そこでの動作は未定義です。
関数の引数の評価順序は規定されていません。関数への引数が順番(1, 2, N)
に評価されるという保証はないため、2 番目の引数が渡される前にインクリメントが評価されるという保証はありません。
したがって、私によると、出力 0 0 は正しいです!!!
いいえ、動作は定義されていないため、出力が 0 0 になると合理的に主張することはできません。
6.5 式の要件に違反しているため、プログラムの動作は未定義です。
前のシーケンス ポイントと次のシーケンス ポイントの間で、オブジェクトの格納値は、式の評価によって最大 1 回変更されます。さらに、以前の値は、保存する値を決定するためにのみ読み取られます。
c++
とc
は両方ともシーケンス ポイントを介在させずに評価され、c
によって格納される値をc++
決定するためと、式 の値を決定するために の前の値が読み取られますc
。
パラメータの評価順序が定義されていないため、動作は確実に未定義になります。ランダムなテストを行うことで、この「未定義の出力」を証明できます。
printf("%d %d\n", c++, c);
// result: 0 1
printf("%d %d %d\n", c++, c, c++);
// result: 1 2 0
printf("%d %d %d %d\n", c++, c++, c++, c);
// result: 2 1 0 3
printf("%d %d %d %d\n", c++, c, c++, c);
// result: 1 2 0 2
printf("%d %d %d %d\n", c++, c, c, c);
// result: 0 1 1 1
このプログラムは、未規定の動作と未定義の動作の両方の組み合わせを示します。unspecified behaviorから始めて、ドラフト C99 標準のセクション6.5
パラグラフに3
は次のように記載されています。
演算子とオペランドのグループ化は、構文によって示されます.74) 後で指定される場合を除き (関数呼び出し ()、&&、||、?:、およびコンマ演算子について)、部分式の評価の順序およびどちらの副作用が発生するかは、どちらも特定されていません。
また、後で指定されたものを除いて具体的に引用しているため、セクション関数呼び出しの段落function-call ()
の草案標準で後で次のように述べていることがわかります。6.5.2.2
10
関数指定子、実引数、および実引数内の部分式の評価順序は指定されていませんが、実際の呼び出しの前にシーケンス ポイントがあります。
したがって、このコード行でCの読み取りまたはC++の評価が最初に行われるかどうかはわかりません。
printf ("%d %d", c++, c);
さらに、セクション6.5.2.4
Postfixインクリメントおよびデクリメント演算子の段落では、次のように2
述べています。
[...]結果が得られた後、オペランドの値がインクリメントされます。[...]オペランドの格納された値を更新する副作用は、前のシーケンス ポイントと次のシーケンス ポイントの間で発生します。
したがって、私たちが知っているのは、ポストインクリメントc
を実行すると、値が読み取られた後、直前の次のシーケンスポイントが呼び出される前に更新されることprintf
だけです。未定義の動作については、ドラフト標準のセクション6.5
段落を見ると、次のように述べられています。2
前のシーケンス ポイントと次のシーケンス ポイントの間で、オブジェクトの格納値は、式の評価によって最大 1 回変更されます。さらに、以前の値は、保存する値を決定するためにのみ読み取られます。
printf
式では、 C++とCc
の両方を評価するために以前の値が読み取られているため、現在は未定義の領域にいます。
あなたは正しいです:それは未定義です。その理由は、 への 3 つの引数が呼び出されるprintf()
前に評価されることは保証されていprintf()
ますが、3 つの引数が評価される順序は定義されていないためです。
ちなみに、インクリメントがセミコロンの後にのみ発生するのは技術的に正しくありません。標準で保証されているのは、インクリメントがセミコロンまでに発生することです。[実際、あなたの場合、制御がprintf()
関数に渡される前にそれが発生することが標準で保証されていると思いますが、この答えは衒学的な雑学の領域に分かれ始めているので、ここで問題を解決させてください! ]
とにかく、要するに、あなたは正しいです。動作は未定義です。
更新: @R.. が正しく観察しているように、未定義の動作は、引数間にシーケンス ポイントがないことが原因です。標準では、未指定および未定義の分詞に関して非常に注意を払っているため、修正は感謝して受け入れられます。