- ベストアンサー
インクリメント演算子をprintf文で使うことについて
◎1-------------------------------------- #include<stdio.h> int main(void) { int i=0; while(i<=20){ printf("%d\t%d\t%d\t%d\t%d\n",++i,++i,++i,++i,++i); } return 0; } ------------------------------------------ ◎2-------------------------------------- #include<stdio.h> int main(void) { int i=0; while(i<=20){ printf("%d\t%d\t%d\t%d\t%d\n",i++,i++,i++,i++,i++); } return 0; } ------------------------------------------ 以上2つのプログラムについて疑問があるのですが、まず◎1についてですが、実行結果は、 5 4 3 2 1 10 9 8 7 6 15 14 13 12 11 20 19 18 17 16 25 24 23 22 21 以上のようになるのですが、自分の考えでは、「printf("%d\t%d\t%d\t%d\t%d\n",i++,i++,i++,i++,i++);」部分の、一番最初の「++i」から処理が始まり、 1 2 3 4 5 ・ ・ ・ のようになる事を期待したのですが、一番最後の「++i」から処理が始まってしまいました。 次に、◎2についてですが実行結果は、 0 0 0 0 0 5 5 5 5 5 10 10 10 10 10 15 15 15 15 15 20 20 20 20 20 以上のようになりました。「printf("%d\t%d\t%d\t%d\t%d\n",i++,i++,i++,i++,i++);」部分で、iをいったん表示してから、iに1を加算するということで、次のiは1になっており、 4 3 2 1 0 ・ ・ ・ のようにまた一番最後の「i++」から処理され以上のようになると思ったのですが、そうはなりませんでした。 以上、◎1と◎2について何故こうなるのかご回答いただければ嬉しいです。
- みんなの回答 (6)
- 専門家の回答
質問者が選んだベストアンサー
既に回答があるように, Java とは異なり C/C++ では引数の評価順序について何も規定されていません. もっというと, 少数の演算子を除いて評価順序の規定は存在しません.つまり, 5個あるインクリメントをどの順に評価するかは全く分からないのです. もっというと, この例では #1 でいわれるように 2つの副作用完了点の間で i の変更が複数回行われているので, その結果は未定義となります. つまり「どのような結果になるか規格では一切関知しない」ということです. なお, この場合「直前の副作用完了点」は前の分の終わりに, 「直後の副作用完了点」は関数 printf の呼び出しの直前にあります. 以下余談ですが, 「わざわざ区別するのも面倒なので, 常に後ろの引数からスタックに積む」処理系もわりと普通のような気がしますがいかがなもんでしょうか>#2. ああ, もちろん「スタックに積む」とは規格のどこにも書かれていないので注意してください.
その他の回答 (5)
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
すでに回答があるように、この動作は、「未定義」とされています。 つまりは、printf() の実行後に i が 5 増えているとすら、断言できないというのが、規格上は正解です。 多くのコンパイラではそうなるでしょうが。
お礼
ご回答ありがとうございます。 みなさまからのご回答にもありましたが、「二つの副作用完了点の間に、オブジェクトの値を2回以上変更している場合」は動作が未定義であるとわかりました!
- asuncion
- ベストアンサー率33% (2127/6289)
当該のprintfの初回実行直後の iの値は5である、としか言えません。
お礼
ご回答ありがとうございます。 動作が未定義ということで、そうとしか言えないという事ですね! 理解しておきます。
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
#2 です。 間違いを書いてしまったので訂正です。(まあ、「例」なので、間違いじゃないと言えなくもないかも) ご指摘を受けて、調べてみたら、そもそも、「引数を積むのは無条件に右から左」がむしろ、主流(少なくともよくある例)のようです。 そういうわけで、 --------------------------- 「多くのコンパイラが、引数を、「左から (先頭から)順に積み上げ、反対側(後ろ から)順に処理する」という挙動をします。 --------------------------- というのは間違いでした。 ※よく考えたら、「多くのコンパイラが」と書いている時点で間違いですがな。
お礼
>「引数を積むのは無条件に右から左」がむしろ、主流(少なくともよくある >例)のようです。 以上のご回答理解しておきます! ありがとうございました。
- 麻野 なぎ(@AsanoNagi)
- ベストアンサー率45% (763/1670)
正解は既に回答があるとおり、「何が 起こるかわからない」「何が起こって も文句は言えない」です。 ただ、具体的な動作の例は、以下のよ うなものがあります。 ※これは、あくまでも、「あるコンパ イラはこういう動きをする」というだ けで、そうでなければ、ならないとか、 そのような動きを期待してもいいとか 言う意味ではありません。 C言語では、多くの場合、関数への引 数渡しは「スタック」が使われます。 結論から言えば、多くのコンパイラが、 引数を、「左から(先頭から)順に積み 上げ、反対側(後ろから)順に処理す る」という挙動をします。 引数の評価は、関数の呼び出し前(引 数をスタックに積み上げるとき)に行 われるので、多くの場合、引数は左 → 右の順に評価されます。 ただ、printf() のような「可変引数」 の関数は事情が異なってきます。 この場合、最初の引数を理解しないと、 引数の数すら把握できません。 従って、「可変引数」の関数は、逆に 「右から(先頭から)順に積み上げ、 反対側(前から)順に処理する」 ということになります。このため、引 数は通常とは逆に、後ろから(右 → 左)の順序で評価されることになります。 あと、++ を前置したときと後置したと きの挙動ですが、入門書には、時々 前置:インクリメントしてから、評価 後置:評価してからインクリメント と書かれていますが、これは、厳密に は間違いです。 正解は、 前置:評価結果はインクリメント後のもの 副作用として、インクリメント実施 後置:評価結果はインクリメントする前の もの、副作用としてインクリメント 実施 になります。 つまり、評価とインクリメントの時間的な 関係までは規定されていないのです。 ですから、 a = (i++) + (j++); の場合、 ・i を評価して、インクリメント ・j を評価して、インクリメント でもいいですし、 ・i を評価 ・j を評価 ・i をインクリメント ・j をインクリメント の順序で動作してもOKです。 今回の例では、 ・i をそれぞれ評価して、 ・あとで、i をそれぞれインクリメント という動作にしているのでしょう。 (ただ、次の式が評価される「前」には終 わっている必要はあります。これが、回答 にある「副作用完了点」というものです) 以上は、あくまでも、「そういう例もある」 「そういう例なら、今回のような結果になる」 というだけで、「これが正しい動作」では ありません。
お礼
ご回答ありがとうございます。 インクリメント処理が副作用、評価結果が副作用完了点とわかりました。 評価とインクリメントの時間的な関係までは規定されていないということもわかりました!
- jacta
- ベストアンサー率26% (845/3158)
副作用完了点から次の副作用完了点までの間に、同一のオブジェクトを複数回更新していますので、未定義の動作を引き起こしています。 簡単にいうと、やってはいけない反則行為を行っていますので、何が起きても仕方がありません。
お礼
ご回答ありがとうございます。 動作が未定義であるということがわかりました。 もっと理解を深めるために、副作用について調べてみます!
お礼
ご回答ありがとうございます。 >引数の評価順序について何も規定されていません. もっというと, 少 >数の演算子を除いて評価順序の規定は存在しません.つまり, 5個ある >インクリメントをどの順に評価するかは全く分からないのです. >もっというと, この例では #1 でいわれるように 2つの副作用完了点 >の間で i の変更が複数回行われているので, その結果は未定義となり>ます. つまり「どのような結果になるか規格では一切関知しない」と >いうことです. 以上、細かくご説明ありがとうございます。 以上のご回答をふまえ、もっと勉強してみます!