100

最近のインタビューで、次の客観的な質問がありました。

int a = 0;
cout << a++ << a;

答え:

を。10b
。01c
。未定義の動作

私は選択肢 b と答えました。つまり、出力は "01" になります。

しかし後で驚いたことに、インタビュアーから、正解はオプション c: 未定義であると言われました。

今、私は C++ のシーケンス ポイントの概念を知っています。次のステートメントの動作は未定義です。

int i = 0;
i += i++ + i++;

しかし、ステートメントに対する私の理解によればcout << a++ << a、は最初に で、後でostream.operator<<()で 2 回呼び出されます。ostream.operator<<(a++)ostream.operator<<(a)

VS2010コンパイラでも結果を確認しましたが、その出力も「01」です。

4

4 に答える 4

147

あなたは考えることができます:

cout << a++ << a;

として:

std::operator<<(std::operator<<(std::cout, a++), a);

C ++は、以前の評価のすべての副作用がシーケンスポイントで実行されることを保証します。関数の引数の評価の間にシーケンスポイントはありません。つまり、引数は引数の前または後aに評価できます。std::operator<<(std::cout, a++)したがって、上記の結果は未定義です。


C++17アップデート

C ++ 17では、ルールが更新されました。特に:

シフト演算子式E1<<E2E1>>E2では、のすべての値の計算と副作用は、のすべての値の計算と副作用のE1前に順序付けられますE2

つまり、結果を生成するコードが必要であり、結果bはを出力します01

詳細については、P0145R3慣用句C++の式の評価順序を調整するを参照してください。

于 2012-05-28T10:17:17.497 に答える
68

技術的には、これは全体として未定義動作です。

しかし、答えには2つの重要な側面があります。

コードステートメント:

std::cout << a++ << a;

次のように評価されます:

std::operator<<(std::operator<<(std::cout, a++), a);

この標準では、関数の引数の評価順序は定義されていません。
だからどちらか:

  • std::operator<<(std::cout, a++)最初に評価されるか
  • a最初に評価されるか
  • 実装で定義された順序である可能性があります。

この順序は、標準では指定されていません[参照1] 。

[参照1] C++035.2.2関数呼び出し
パラ8

引数の評価の順序は指定されていません。引数式の評価のすべての副作用は、関数に入る前に有効になります。後置式と引数式リストの評価順序は未定です。

さらに、関数への引数の評価の間にシーケンスポイントはありませんが、シーケンスポイントは、すべての引数の評価後にのみ存在します[参照2]

[参照2] C++ 03 1.9プログラムの実行[intro.execution]:
パラ17:

関数を呼び出す場合(関数がインラインであるかどうかに関係なく)、すべての関数の引数(存在する場合)の評価後、関数本体の式またはステートメントの実行前に実行されるシーケンスポイントがあります。

ここで、の値はc、介在するシーケンスポイントなしで複数回アクセスされていることに注意してください。これに関して、標準では次のように述べられています。

[参照3] C++ 03 5式[expr]:
パラ4:

....
前のシーケンスポイントと次のシーケンスポイントの間で、スカラーオブジェクトは、式の評価によって、格納されている値を最大で1回変更する必要があります。さらに、前の値は、保存される値を決定するためにのみアクセスされるものとします。この段落の要件は、完全な式の部分式の許容される順序ごとに満たされるものとします。それ以外の場合、動作は未定義です。

コードはc、シーケンスポイントを介さずに複数回変更され、保存されたオブジェクトの値を決定するためにアクセスされていません。これは上記の条項の明らかな違反であり、したがって、標準で義務付けられている結果は未定義動作です[参照3]

于 2012-05-28T10:16:27.537 に答える
20

シーケンス ポイントは部分的な順序付けのみを定義します。あなたの場合、(オーバーロードの解決が完了すると)次のようになります。

std::cout.operator<<( a++ ).operator<<( a );

a++と の最初の呼び出しの 間にはstd::ostream::operator<<シーケンス ポイントがあり、 の 2 番目aと 2 番目の呼び出しの間にはシーケンス ポイントがありますが、 と の間にはシーケンス ポイントがありstd::ostream::operator<<ません。唯一の順序付け制約は、への最初の呼び出しの前に (副作用を含めて)完全に評価されること、およびへの 2 番目の呼び出しの前に 2 番目の制約が完全に評価されることです。(因果的な順序付けの制約もあります。引数として最初の結果を必要とするため、2 番目の呼び出しを最初の呼び出しより前にすることはできません。) §5/4 (C++03) は次のように述べています。a++aa++operator<<aoperator<<operator<<

特に明記されていない限り、個々の演算子のオペランドと個々の式の部分式の評価の順序、および副作用が発生する順序は規定されていません。前のシーケンス ポイントと次のシーケンス ポイントの間で、スカラー オブジェクトの格納値は、式の評価によって最大 1 回変更されます。さらに、以前の値は、保存する値を決定するためにのみアクセスされます。この段落の要件は、完全な式の部分式の許容される順序ごとに満たされるものとします。それ以外の場合、動作は未定義です。

式の許容される順序の 1 つはa++a, の最初の呼び出しoperator<<、 の 2 番目の呼び出しoperator<<です。aこれは( )の格納された値を変更しa++、新しい値 (2 番目の ) を決定する以外にアクセスしaます。動作は未定義です。

于 2012-05-28T11:27:27.860 に答える
4

正しい答えは、質問に質問することです。読者には明確な答えが見えないため、このステートメントは受け入れられません。別の見方をすると、ステートメントの解釈を非常に困難にする副作用 (c++) を導入したことです。簡潔なコードは、その意味が明確であれば素晴らしいものです。

于 2012-05-29T21:06:08.217 に答える