i = ++i + ++i;C++で
-
19-08-2019 - |
質問
このコードが 14 を出力する理由を誰か説明してもらえますか?他の生徒から質問されたのですが、分かりませんでした。
int i = 5;
i = ++i + ++i;
cout<<i;
解決
C ++では、副作用の順序は未定義です。さらに、1つの式で変数を2回変更しても、動作は定義されていません( C ++標準、<!>#167; 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 ++ FAQ Lite 。
一部の回答/コメントでは、「未定義の動作」の意味と、それによってプログラムが無効になるかどうかについて議論されています。そこで、標準に記載されていることをいくつかのメモとともに正確に詳しく説明した、このかなり長い回答を投稿します。あまり退屈じゃないといいのですが…
標準の引用符で囲まれた部分は、現在の C++ 標準 (ISO/IEC 14882:2003) に由来しています。C 標準にも同様の文言があります。
C++ 標準によれば、シーケンス ポイントのセット内で値を複数回変更すると、未定義の動作が発生します (セクション 5 の段落 4)。
特に断りのない限り、 個々のオペランドの評価 演算子と部分式 個々の式、および順序 副作用が起こるのは、 unspecified.53) の 次のシーケンスはスカラーを指しています object は、格納された値を持つものとします 最大で一度だけ、 式の評価。さらに、前の値は 値を決定するためだけにアクセスされる 保存されます。この要件 段落は、それぞれについて満たさなければならない の許容される順序 完全な式の部分式。それ以外の場合、動作は未定義です。[例:
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
—終わりの例]
2 番目の例に注意してください。i = 7, i++, i++;
カンマ演算子はシーケンスポイントであるため、" が定義されます。
C++ 標準では、「未定義の動作」の意味は次のとおりです。
1.3.12 未定義の動作 [defns.unknown]
誤ったプログラム構造や誤ったデータの使用時に発生する可能性のある動作など、この 国際規格は要件を課しません。未定義の動作は、この 国際規格では、行動の明示的な定義の説明が省略されています。[注記:許容される未定義 行動は、予測できない結果をもたらす状況を完全に無視することから、 環境に特徴的な文書化された方法での翻訳またはプログラムの実行( 診断メッセージの発行)、変換または実行の終了 ( 診断メッセージ)。多くの誤ったプログラム構成は、未定義の動作を引き起こしません。彼らは診断を受ける必要があります。】
言い換えれば、コンパイラは、次のようなことを自由に実行できます。
- エラーメッセージを吐き出し、
- 実装が定義され文書化された何かを行う、
- まったく予測できない結果になる
2 番目の項目では、ほとんどのコンパイラーに備わっている言語拡張機能について説明しますが、もちろん標準では定義されていません。
したがって、厳密に言えば、未定義の動作を示すものは「違法」ではないと思いますが、私の経験では、C/C++ プログラム内に「未定義の動作」を示すものがあった場合 (拡張機能でない限り)、それはバグです。このような構成を違法と呼ぶことは、混乱を招く、誤解を招く、または見当違いではないと思います。
また、コンパイラが値 14 に到達するために何をしているのかを説明することは、要点を逸脱しているため、あまり役に立たないと思います。コンパイラはほとんど何でも行うことができます。実際、異なる最適化オプションを使用してコンパイラを実行すると、コンパイラが異なる結果に達する可能性があります (または、クラッシュするコードが生成される可能性があります。誰にもわかりません)。
追加の参考文献や権威者へのアピールが必要な方のために、以下にいくつかのヒントを示します。
Steve Summit (comp.lang.c のよくある質問の管理者) の 1995 年のこのトピックに関する長い長い回答:
この件に関してビャルネ・ストロイストラップ氏は次のように述べている。
脚注:C++ 標準では、「違法」という単語が 1 回だけ使用されています - の使用に関して C++ と標準 C の違いを説明するとき static
または extern
型宣言付き。
シンプル...コンパイラは、中間結果をキャッシュせずに、合計を実行する前に BOTH 増分を評価しています。つまり、iを2回追加すると、値は7になります。
行う場合
int j=++i;
int k=++i;
i = j+k;
予想どおり13が表示されます。
特定のコンパイラでは、最初に両方の++操作を実行し、次に追加することを選択しています。コードを次のように解釈しています:
int i = 5;
++i;
++i;
i = i + i;
cout << i;
それは完全に有効です。
より良い質問は、常に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;
わずかにより理にかなっているので、おそらく<=>になるでしょう。
構文ツリーから問題を見ると、問題に対する答えがより明確になると思います:
i
|
=
|
+
|
単項式-単項式
単項式:単項演算子式
この場合、式は変数iに要約されます。
これで、両方の単項式が同じオペランドを変更するため、両方の単項式の結果を追加する前に、単項式を評価するときにコードで++ iが2回実行されます。
したがって、コードが実際に行うことは
++ i;
++ i;
i = i + i;
i = 5の場合、
i = i + 1; // i <!> lt;-6
i = i + 1; // i <!> lt;-7
i = i + i; // i <!> lt;-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