[解決済み] 実際には、なぜコンパイラによってint x = ++i ++i; の計算値が異なるのでしょうか?
質問
このコードを考えてみましょう。
int i = 1;
int x = ++i + ++i;
このコードがコンパイルされると仮定して、コンパイラが何をするかについて、いくつかの推測があります。
-
両方
++i
戻る2
となり、その結果x=4
. -
一
++i
リターン2
を返し、もう一方は3
となり、結果としてx=5
. -
両方
++i
戻る3
となり、その結果x=6
.
私には、2番目の可能性が最も高いと思われます。2つのうち1つは
++
演算子が実行され
i = 1
を使用する場合、その
i
がインクリメントされ、その結果
2
が返されます。次に、2番目の
++
演算子が実行され
i = 2
の場合、その
i
がインクリメントされ、その結果
3
が返されます。次に
2
と
3
を足すと
5
.
しかし、このコードをVisual Studioで実行したところ、結果は
6
. 私はコンパイラについてもっと理解しようとしているのですが、何がどうなって
6
. 私の唯一の推測は、コードが何らかの "built-in" 並行処理で実行される可能性があるということです。2つの
++
演算子が呼び出され、それぞれインクリメントされた
i
が返され、その後、両者とも
3
. これは、私のコールスタックの理解に反するので、説明する必要があります。
はどのような(合理的な)ことができるのでしょうか?
C++
という結果になるようなコンパイラを使用します。
4
または結果や
6
?
備考
この例は、Bjarne Stroustrup著「Programming」に未定義の動作の例として掲載されています。Principles and Practice using C++ (C++ 14)に未定義動作の例として掲載されています。
参照 シナモンさんのコメント .
解決方法は?
コンパイラは、あなたのコードを非常に単純な命令に分割し、最適と思われる方法でそれらを再結合し、配置します。
コード
int i = 1;
int x = ++i + ++i;
は、以下の命令で構成されています。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
5. read i as tmp2
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
しかし、私の書き方では番号付きリストであるにもかかわらず、わずか数個の 順序依存性 ここで 1->2->3->4->5->10->11 と 1->6->7->8->9->10->11 は相対順序のままでなければならないのです。それ以外は、コンパイラが自由に並び替えを行い、おそらく冗長性を排除することができます。
例えば、このような順序にすることができます。
1. store 1 in i
2. read i as tmp1
6. read i as tmp3
3. add 1 to tmp1
7. add 1 to tmp3
4. store tmp1 in i
8. store tmp3 in i
5. read i as tmp2
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
なぜコンパイラはこんなことができるのでしょうか?インクリメントの副作用にシーケンスがないからです。例えば、4ではデッドストアがあり、値はすぐに上書きされます。また、tmp2とtmp4は実際には同じものです。
1. store 1 in i
2. read i as tmp1
6. read i as tmp3
3. add 1 to tmp1
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
これで、tmp1に関するものはすべてデッドコードとなり、使われることはない。そして、iの再読み込みもなくすことができます。
1. store 1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
10. add tmp3 and tmp3, as tmp5
11. store tmp5 in x
見てください、このコードはずっと短くなっています。オプティマイザは喜んでいる。しかし、プログラマーはそうではない。なぜなら、iは一度しかインクリメントされなかったからだ。おっとっと。
では、コンパイラが代わりにできる他のことを見てみましょう:元のバージョンに戻ってみましょう。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
5. read i as tmp2
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
コンパイラはこのように並べ替えることができる。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
9. read i as tmp4
10. add tmp2 and tmp4, as tmp5
11. store tmp5 in x
で、iが2回読まれていることに再度気づくので、片方を削除します。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp3
7. add 1 to tmp3
8. store tmp3 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
それはいいのですが、さらに進んで、tmp1を再利用することができます。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
6. read i as tmp1
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
そうすると、6のiの再読込をなくすことができる。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
4. store tmp1 in i
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
これで4は死んだ店だ。
1. store 1 in i
2. read i as tmp1
3. add 1 to tmp1
7. add 1 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
となり、3と7が1つの命令に統合されるようになりました。
1. store 1 in i
2. read i as tmp1
3+7. add 2 to tmp1
8. store tmp1 in i
5. read i as tmp2
10. add tmp2 and tmp2, as tmp5
11. store tmp5 in x
最後のテンポラリーをなくす。
1. store 1 in i
2. read i as tmp1
3+7. add 2 to tmp1
8. store tmp1 in i
10. add tmp1 and tmp1, as tmp5
11. store tmp5 in x
そして、今度はVisual C++が出している結果を得ることができます。
どちらの最適化パスでも、何もしないために命令が削除されない限り、重要な順序依存性は維持されていることに注意してください。
関連
-
[解決済み] using namespace std;」はなぜバッドプラクティスだと言われるのですか?
-
[解決済み] 8192個の要素にループをかけると、プログラムが遅くなるのはなぜですか?
-
[解決済み] なぜ (int)x ではなく static_cast<int>(x) を使うのですか?
-
[解決済み] C言語とC++の両方で有効なコードを、それぞれの言語でコンパイルすると、異なる動作になることがありますか?
-
[解決済み] C++の規格では、初期化されていないboolがプログラムをクラッシュさせることは可能ですか?
-
[解決済み] なぜこのプログラムは3つのC++コンパイラで誤って拒否されるのですか?
-
[解決済み] 関数からunique_ptrを返す
-
[解決済み] C++11でconstexpr機能はいつ使うべきですか?
-
[解決済み] Intel CPU の _mm_popcnt_u64 で、32 ビットのループカウンターを 64 ビットに置き換えると、パフォーマンスが著しく低下します。
-
[解決済み】f(i = -1, i = -1)の挙動が未定義なのはなぜ?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】コンストラクターでのエラー:識別子を期待されますか?
-
[解決済み】非静的メンバ関数への参照を呼び出す必要がある
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み】C++のGetlineの問題(オーバーロードされた関数 "getline "のインスタンスがない
-
[解決済み】テンプレートの引数1が無効です(Code::Blocks Win Vista) - テンプレートは使いません。
-
[解決済み] 式はクラス型を持つ必要があります。
-
[解決済み】C++ - 適切なデフォルトコンストラクタがない [重複]。
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み] 変数サイズのオブジェクトが初期化されないことがある c++
-
[解決済み】なぜこれらのコンストラクトはプリインクリメントとポストインクリメントを使用して未定義の動作をしているのでしょうか?