電子書籍の厳選無料作品が豊富!

C言語では ++a と a++ は違う意味を持つらしいので、実行結果がどのように変わるのか試してみました。

(1) int a = 10 ;
   int b = ++a ;

(2) int a = 10 ;
   int b = a++ ;

(1)と(2)では、aとbの値はそれぞれ次のようになりました。

(1) a: 11
   b: 11

(2) a: 11
   b: 10

(1)と(2)は予想通りの結果になりました。今度は次の式を試してみました。

(3) int a = 10 ;
   int b = ++a + ++a ;

(4) int a = 10 ;
   int b = ++a + a++ ;

(5) int a = 10 ;
   int b = a++ + ++a ;

(6) int a = 10 ;
   int b = a++ + a++ ;

(3)~(6)のbの値は次のように予想しました。

(3) (10+1) + (11+1) = 23

(4) (10+1) + 11 = 22

(5) 10 + (11+1) = 22

(6) 10 + 11 = 21

しかし、実際は次の値になりました。

(3) a: 12
   b: 24

(4) a: 12
   b: 22

(5) a: 12
   b: 22

(6) a: 12
   b: 20

(4)と(5)は予想通りの結果になりましたが、(3)と(6)は予想と違っていました。
どうしてこのような値になったのでしょうか?

A 回答 (7件)

ちょっと補足すると……。



入門書には、よく、

++a は、「a のインクリメントをしてから、その値を使う」
a++ は、「a の値を使ったあとで、a をインクリメントする」
と書かれていることがあります。

実は、これは、ちょっと違うのですね。
Cでは、他の言語では文と呼んでいるものでも、(たとえば代入でも)「式」と呼ばれその値をもっています。
実は、正しくは、

++a は、「・a をインクリメントする、・式の値は a をインクリメントしたもの」
a++ は、「・a をインクリメントする、・式の値は a の値」

となります。(どちらが先とかないのですね)
そこで、「インクリメントする」のは実際にはどのタイミングかと言えば、「副作用完了点」までには終わることが保証されています。おおざっぱに言えば、; までとか、関数の括弧の中とか。

ですから、たとえば、
a++ を

・a++ が出てくるたびに、a をインクリメントする
・a++ があったら、式の評価は a を使う。副作用完了点の直前で、a をインクリメントする
のどちらでも、規格には合致するわけです。

さらに、「規格では『副作用完了点までにa++は2度以上出てこない』から、その前提でコンパイラを書いているので、a++ が2回出てきたら、こける」というのも考えられます(バグではない点に注意)

こういうことで、「インクリメントしてから云々」という解説も、あるいは、誤解の元かなと思います。

同じようなことは、括弧や演算子の(結合の)優先順序にも言えて、入門書で、
・括弧の中は「先に」計算する
・乗除は加減より「先に」計算する
という記述があることもあります。

これも、実は、「括弧の中は『強く』結合する」だけで、括弧の外だけを先に計算しておいて、あとから、括弧の中を計算するということも、実はあるかもしれません。
    • good
    • 2
この回答へのお礼

なるほど、++aやa++が2回以上出てこない事を前提にして定義しているのですね。
インクリメントするタイミングまでは定められていないのですね。

回答有難うございました。

お礼日時:2011/05/24 04:49

>同じコンパイラの同じバージョンで行う限り、結果は一定するでしょうが



とは限りません、コンパイル時のオプション指定(最適化関係)、コンパイル時のメモリの空きサイズなどで結果は変わります。

コンパイルしてみるまでどうなるか分かりません。
    • good
    • 0
この回答へのお礼

同じコンパイラの同じバージョンでも結果が異なる事があるのですね。
回答有難うございました。

お礼日時:2011/05/24 04:54

うぃ, 特定の演算子 (論理演算子の &&, || とコンマ演算子) 以外ではオペランドの評価順序は定義されていませんし, 関数の実引数の評価順序も決まってません>#3. 実際には「処理系定義」じゃなくて「未定義」なので「全く同じ式であっても位置によって順序が違う」ということも許されています.



Java なら「常に左から右」と決まってるんだけどね.

ただし, 「関数を呼び出す前に実引数に伴う副作用は全て適用されている」ことは保証されてますし,
scanf(%d %d", &yr, &profit[yr]);
も動作は確定しています (規格に準拠した脳内コンパイラがあれば大丈夫).

ちなみに「副作用のない」ように書くのはほぼ不可能....
    • good
    • 0
この回答へのお礼

「処理系定義」ではなく「未定義」ですか。
自分には違いがよく分からないけど・・・

回答有難うございました。

お礼日時:2011/05/24 02:36

他の方が書いているように、言語仕様では未定義動作です。

何が起こるかわからない。
具体的に何故そのようになるのかは、コンパイラの処理の仕方による。

(3)だと、gccコンパイラで最適化しないと、こんな感じ。
a ← 10
レジスタ ← a
レジスタ += a
b ← レジスタ
a += 1
a += 1

二項演算子の左項と右項をどちらを先に評価するかとか、複数の実引数を使った関数呼び出しでどの順序に実引数を評価するかとか、等も未定義のはずです。

なぜ仕様上で未定義になっているかというと、コンパイラの最適化をうまく働かせるためです。
    • good
    • 0
この回答へのお礼

なるほど、最適化し易くする為に未定義にしているのですか。
この処理は未定義だから予測不可能なのですね。

回答有難うございました。

お礼日時:2011/05/23 00:23

少し追記しておきますと、C言語のこういった動作は基本的に実装系依存です。



同じコンパイラの同じバージョンで行う限り、結果は一定するでしょうが、別のコンパイラや別のバージョンで試した時に結果が変わることがあり得ます。
そういった点に注意して、副作用のない書き方を心掛ける必要があります。
    • good
    • 0
この回答へのお礼

これって実装系依存なのですか。
「式の前や後でインクリメントされる」のは自分が用いたコンパイラで実行した場合なんですね。

再度回答有難うございました。

お礼日時:2011/05/22 13:00

いい実験ですね。



前置は評価が行われる前にインクリメントされますよね。
>int b = ++a + ++a ;
a+=1;
a+=1;
int b = a+a;

>int b = a++ + a++ ;
後置は評価が行われた後にインクリメントされます。
int b = a+a;
a+=1;
a+=1;

しかし、これは実験なので良いですが、実際のコーディングではこういった「副作用を伴いうる書き方」はしない方が無難です。
例えば下記のようなものですね。
出典は「プログラミング作法」です。
> str[i++] = str[i++] = ' ';
> array[i++] = i;
> scanf(%d %d", &yr, &profit[yr]);

参考URL:http://www.amazon.co.jp/dp/4756136494/
    • good
    • 0
この回答へのお礼

なるほど、式の前や後でインクリメントされていたのですね。
今回は式の中にインクリメントを織り交ぜたらどうなるか気になったので実験用としてプログラミングしてみましたが、確かに実際のコーディングではこのような書き方はしない方が良さそうですね。

回答有難うございました。

お礼日時:2011/05/22 12:52

「やってはいけない」ことをしちゃったから.



詳細は面倒なので全部省きますが, ぶっちゃけて言うと
「同じオブジェクト (変数など) を 1つの式で 2回以上変化させたらダメ」
ということになっています. つまり, (3)~(6) はすべてこの「ダメなこと」をしちゃっているので, 結果として
「どうなるか誰も知らない」
ことになっています.

念のため言っておくと, (4) と (5) で「予想通りの結果になった」というのも「偶然」だと思ってください. この結果が言語として保証されているわけではありません.
    • good
    • 1
この回答へのお礼

これって駄目な事だったんですか。結果は誰も予測出来ないのですね。
回答有難うございました。

お礼日時:2011/05/22 12:37

お探しのQ&Aが見つからない時は、教えて!gooで質問しましょう!