インクリメントの挙動

int i=0;
i = i++ + ++i;

というものを見つけまして、すぐにiに入る値が想像できなかったので、
こういうときは実際に実行してみれば判ると思い、普通にコードを書いた。
ちなみに、0+2が行われるので2が入ると予想してました。

追記

これは未定義な動作と教えていただきました。(thx, id:qnighy)
http://en.wikipedia.org/wiki/Undefined_behavior



実験

#include <stdio.h>

int main(){
	int i=0;
	i=i++ + ++i;
	printf("%d\n", i);
}

3

あ・・・あれ・・・?
演算は左から行われるのだから、0+2になるんじゃないのか。


デバッガで中身を見るとこんな感じになってた

004013C0  /$ 55             PUSH EBP
004013C1  |. 89E5           MOV EBP,ESP
004013C3  |. 83E4 F0        AND ESP,FFFFFFF0
004013C6  |. 83EC 20        SUB ESP,20
004013C9  |. E8 52060000    CALL c.00401A20
004013CE  |. C74424 1C 0000>MOV DWORD PTR SS:[ESP+1C],0              ; | i=0;
004013D6  |. FF4424 1C      INC DWORD PTR SS:[ESP+1C]                ; | ++i;
004013DA  |. 8B4424 1C      MOV EAX,DWORD PTR SS:[ESP+1C]            ; | eax = i;
004013DE  |. 01C0           ADD EAX,EAX                              ; | eax += eax;
004013E0  |. 894424 1C      MOV DWORD PTR SS:[ESP+1C],EAX            ; | i = eax;
004013E4  |. FF4424 1C      INC DWORD PTR SS:[ESP+1C]                ; | ++i;
004013E8  |. 8B4424 1C      MOV EAX,DWORD PTR SS:[ESP+1C]            ; | 
004013EC  |. 894424 04      MOV DWORD PTR SS:[ESP+4],EAX             ; |
004013F0  |. C70424 6430400>MOV DWORD PTR SS:[ESP],c.00403064        ; |ASCII "%d"
004013F7  |. E8 94080000    CALL                 ; \printf
004013FC  |. C9             LEAVE
004013FD  \. C3             RETN

i=0のとき、i++ + ++iを分解すると、
i=i++;の結果はi=1となり、i+=++iは、i+=(1+i)となって、i+=i;i+=1;としていると予想。
i=i++;の演算を行った後、
その計算結果をレジスタeaxにいれて、
i + i = eax + eaxを行い、
その結果をiに代入した後に、
++iの評価が行われることで、iが3になった感じですかね。
最後にeaxにいれてるのは、printfの引数として渡すために、一度レジスタにコピーしているだけです。
スタックからスタックへのコピーはできないので、一度レジスタを経由しています。
ちなみにこの式は最適化オプションをつけると、定数展開されます。コンパイラ君は賢いですねえ。


でも少し疑問がでてきた。
i=++i + i++を行った場合どうなるのか?
一度変数に代入せず、この演算の結果を直接引数にしてみたらどうなるのか?
この組み合わせは4パターンあるので、全部やってみた。

#include <stdio.h>

int main(){
	int i=0;
	
	i=i++ + ++i;
	printf("%d\n", i);
	
	i=0;
	i = ++i + i++;
	printf("%d\n", i);
	
	i=0;
	printf("%d\n", i++ + ++i);
	
	i=0;
	printf("%d\n", ++i + i++);
}

3
3
2
2

i++ + ++iと++i + i++というように、左辺と右辺を入れ替えても、結果は同じということらしい。
デバッガで見てみると、どちらも同じコードを生成していました。
で、引数として、演算結果を直接渡した場合、2になるらしい。
もう一個変数を作って、演算結果をiではなくそっちにいれるようにすれば、同じく2になった。


ほほー。


ならPerlでも同じなのかな!

$i=0;
$i = $i++ + ++$i;
printf("%d\n", $i);

$i=0;
$i = ++$i + $i++;
printf("%d\n", $i);

$i=0;
printf("%d\n", $i++ + ++$i);

$i=0;
printf("%d\n", ++$i + $i++);

2
3
2
3

違うやん・・・

まとめ

結果がわからない演算はするもんじゃない。