このコードが 14 を出力する理由を誰かに説明してもらえますか? 他の学生から質問されたのですが、よくわかりませんでした。
int i = 5;
i = ++i + ++i;
cout<<i;
このコードが 14 を出力する理由を誰かに説明してもらえますか? 他の学生から質問されたのですが、よくわかりませんでした。
int i = 5;
i = ++i + ++i;
cout<<i;
副作用の順序はC++では定義されていません。さらに、1つの式で変数を2回変更しても、動作は定義されていません(C ++標準、§5.0.4、物理ページ87 /論理ページ73を参照)。
解決策:複雑な表現では副作用を使用しないでください。単純な表現では複数の副作用を使用しないでください。また、コンパイラが提供するすべての警告を有効にしても問題はありません。コマンドラインに-Wall
(gcc)または/Wall /W4
(Visual C ++)を追加すると、適切な警告が表示されます。
test-so-side-effects.c: In function 'main':
test-so-side-effects.c:5: warning: operation on 'i' may be undefined
test-so-side-effects.c:5: warning: operation on 'i' may be undefined
明らかに、コードは次のようにコンパイルされます。
i = i + 1;
i = i + 1;
i = i + i;
これは未定義の動作であり、使用するコンパイラによって結果が異なります。たとえば、C ++FAQLiteを参照してください。
一部の回答/コメントでは、「未定義の動作」の意味と、それがプログラムを無効にするかどうかについての議論がありました。そのため、このかなり長い回答を投稿して、標準が何を言っているのかを正確に説明しています。あまり退屈でないことを願っています...
標準の引用部分は、現在の C++ 標準 (ISO/IEC 14882:2003) からのものです。C標準にも同様の文言があります。
C++ 標準によると、シーケンス ポイントのセット内で値を複数回変更すると、未定義の動作が発生します (セクション 5 パラグラフ 4)。
特に明記されていない限り、個々の演算子のオペランドと個々の式の部分式の評価の順序、および副作用が発生する順序は規定されていません。式の評価によって最大 1 回。さらに、保存する値を決定するためにのみ、前の値にアクセスする必要があります。この段落の要件は、完全な式の部分式の許容される順序ごとに満たされるものとします。それ以外の場合、動作は未定義です。[例:
i = v[i++]; // the behavior is unspecified i = 7, i++, i++; // i becomes 9 i = ++i + 1; // the behavior is unspecified i = i + 1; // the value of i is incremented
—終わりの例]
i = 7, i++, i++;
コンマ演算子がシーケンス ポイントであるため、2 番目の例 " " が定義されていることに注意してください。
C++ 標準で「未定義の動作」とは次のことを意味します。
1.3.12 未定義の動作 [defns.undefined]
誤ったプログラム構成または誤ったデータを使用したときに発生する可能性があるような、この国際規格が要件を課していない動作。この国際規格が動作の明示的な定義の記述を省略している場合も、未定義の動作が予想される場合があります。[注: 許容される未定義の動作は、状況を完全に無視して予測不可能な結果をもたらすことから、翻訳中またはプログラム実行中に環境に特有の文書化された方法で動作すること (診断メッセージの発行の有無にかかわらず)、翻訳または実行の終了 (診断メッセージが発行されます)。多くの誤ったプログラム構造は、未定義の動作を引き起こしません。それらは診断される必要があります。]
言い換えれば、コンパイラーは、以下を含め、必要なことを自由に行うことができます。
2 番目の項目は、ほとんどのコンパイラが持っている言語拡張機能をカバーしていますが、もちろん標準では定義されていません。
したがって、厳密に言えば、未定義の動作を示すものは「違法」ではないと思いますが、私の経験では、C/C++ プログラムに「未定義の動作」を示すものがあるときはいつでも (それが拡張機能でない限り)、それはバグです。そのような構造を違法と呼ぶことは、混乱、誤解、または誤解を招くものではないと思います。
また、値 14 に到達するためにコンパイラが何を行っているかを説明しようとすることは、的外れであるため、特に役に立たないと思います。コンパイラはほとんど何でもできます。実際、異なる最適化オプションを使用して実行すると、コンパイラが異なる結果に到達する可能性があります (または、クラッシュするコードを生成する可能性があります - 誰にもわかりません)。
追加の参照や権威へのアピールが必要な人のために、ここにいくつかの指針があります。
スティーブ サミット (comp.lang.c よくある質問の管理者) の 1995 年からのこのトピックに関する長い、長い回答:
この件について、Bjarne Stroustrup は次のように述べています。
脚注: C++ 標準では、型宣言の使用static
または使用に関する C++ と標準 C の違いを説明するときに、「違法」という言葉を 1 回だけ使用します。extern
単純です...コンパイラは、中間結果をキャッシュせずに、合計を実行する前に両方の増分を評価しています。これは、iを2回追加すると、値が7になることを意味します。
もし、するなら
int j=++i;
int k=++i;
i = j+k;
期待どおりに13が表示されます。
特定のコンパイラでは、最初に両方の++操作を実行し、次に加算を実行することを選択しています。コードを次のように解釈しています。
int i = 5;
++i;
++i;
i = i + i;
cout << i;
それは完全に有効です。
構文木から問題を見ると、問題の答えがより明確になると思います。
私
|
=
|
+
|
単項式-単項式単項式
:単項演算子式
この場合、式は変数iに要約されます。
ここで、両方の単項式が同じオペランドを変更するため、コードは、両方の単項式の結果を追加する前に、単項式を評価するときに++iを2回実行します。
したがって、コードが行うことは確かに
++iです。
++ i;
i = i + i;
i = 5の場合、これは
i = i+1を意味します。// i <-6
i = i + 1; // i <-7
i = i + i; // i <-14
より良い質問は、常にそうなるの14
ですか?
int i = 5;
i = ++i + ++i;
cout<<i;
i = ++i + ++i ;
i = ++(5) + ++(5) ;
i = 6 + 6 ;
i = 12;
i = ++i + ++i ;
i = ++i + ++(5) ;
i = ++i + (6) ;
i = ++(6) + 6 ;
i = (7) + 6 ;
i = 13;
i = ++i + ++i ;
i = ++i + ++(5) ;
i = ++(6) + (6) ;
i = (7) + (7) ;
i = 14;
おそらく14
、それはおそらくです。
プレフィックスの増分が優先されるため:
int i = 5;
i = i+1; // First ++i, i is now 6
i = i+1; // Second ++i, i is now 7
i = i + i // i = 7 + 7
cout << i // i = 14
i = i++ + i; //11
i = i++ + i++; //12
i = i++ + ++i; //13
i = ++i + i++; //13
i = ++i + ++i; //14