【补充声明】此文完成于几年前回答 BCCN 论坛的网友提问,就问题本身而言,对于这个问题似乎是没必要深究的,因为这种代码在读取一个变量的值的过程中反复尝试修改它的值,其结果依赖编辑器的实现。这种代码当然也是不可能在现实应用中出现的。不过作为一个问题,如果他一定要问,某编译器为什么会给出这样的结果,那就必须了解编译器对这个代码的编译结果细节,这就是本文所论述的东西。本文只涉及到了 TC2, VC6, VC2005 几种编译器。而且后两者应该使用的 WIN32 DEBUG,通常 Release 版本和 Debug 版本是一种在运行结果表现上的等效关系,对这个具体问题在当时我并未有精力再去细分。此次更新编辑顺便修改了原文中的个别错别字。原文中的术语“堆栈”修改为“栈”。原文中的一些中间结论不够准确,但暂未删除,已经用删除线做了处理。
---- hoodlum1980 ,2012年9月10日。
首先我们来看一下这个问题的提出,来自于一个网友的提问:
http://bbs.bccn.net/thread-200774-1-1.html
----------------------------------------------------------------------------------------------------------
可见,++i和--i执行的时候直接改变了i的值,而i++和i--必须在所在的这个语句执行后才能改变i的值,所以i++作为参数时,实际上是这样的过程,
printf("%d",i++);
相当于下面的语句:
int temp=i;
i = (i+1);
printf("%d",temp);
因此上面的代码可以翻译为:
int i=8;
printf("%d,%d,%d,%d",++i,--i,i++,i--);
因此可以翻译为下面的等效代码:
i=8;
temp0=i; //temp0=8;
i--; //7
temp1=i; //temp1=7
i++; //8
--i; //7
++i; //i=8
printf("%d,%d,%d,%d",i,i,temp1,temp0);
所以打印结果是8,8,7,8
可见,上面的行为可以翻译为下面的等效代码(TC2.0):
i=8;
temp0=i; //这时8已经入栈,实际上通过ax寄存器直接压栈里了~~~,不存在temp0)
i--; //i=7
temp1=i; //这时7已经入栈,实际上通过ax寄存器直接压栈里了~~~,不存在temp1)
i++; //i=8
--i; //i=7
temp2=i; //7已经入栈
++i; //i=8
temp3=i; //8已经入栈
printf("%d,%d,%d,%d",temp3,temp2,temp1,temp0);
输出结果是:8,7,7,8
请注意两者的区别主要是,他们是一边处理自增自减并一边随时入栈,还是先处理完所有自增自减之后再最后统一一次性的入栈。
----------------------------------------------------------
【补充】:在这里这样总结他们的区别是不完善的,实际上涉及到(1)缓存 i 的值,(2)++/--运算符的执行,(3)push i 或 缓存值。
这三者之间的顺序问题。以上三者之间的顺序的微小差别都能对结果产生关键影响。请参考本文最后的补充。
--hoodlum1980 @ 2011年10月12日
----------------------------------------------------------
(1)在TC下面属于前者,每执行一个语句,就把i通过ax寄存器马上入栈了,所以参数入栈和i++等语句是交叉交替性进行的。这里的i++和++i的主要区别在于压栈是在i自增之前还是之后。
i++相当于:先入栈,再自增。
++i相当于,先自增,再入栈。
所以我们看到下面的参数:从右到左:
i--: 入栈8,i=7
i++:入栈7,i=8
--i:i=7,入栈7
++i:i=8,入栈8
所以导致栈里面的参数是8,7,7,8,所以打印结果是8,7,7,8.
(2)在上面的VC.net2005中属于后者,是先为i++和i--保存值,然后执行完所有的自增和自减,最后一次性的把所有参数入栈。在这里i++和++i的区别
主要是是否把i的值保存到另一个位置:而且最大不同点在于这里不马上入栈,而是等所有参数处理后统一入栈。
i++:先缓存i的原始值,然后i自增。最后入栈时,用i的原始值入栈。
++i:i自增,不缓存i的原始值。最后入栈时,是更新后的i。
所以我们看到在VC2005.NET中的顺序是:
i--: 缓存8,i=7
i++:缓存7,i=8
--i:i=7
++i:i=8
参数一次性依次入栈:第一个缓存值8,第二个缓存值7,i的当前值8,i的当前值8。
所以这时候栈的数据是:8,8,7,8.(从左到右)。
所以打印结果是:8,8,7,8.
------------------------------------------------------------
.text:00401028 mov [ebp+var_4], 8 ; int i = 8;
.text:0040102F mov eax, [ebp+var_4]
.text:00401032 mov [ebp+var_8], eax ; int tmp1 = i;
.text:00401035 mov ecx, [ebp+var_8]
.text:00401038 push ecx ; push tmp1; (8)
.text:00401039 mov edx, [ebp+var_4] ; int tmp2 = i;
.text:0040103C mov [ebp+var_C], edx
.text:0040103F mov eax, [ebp+var_C]
.text:00401042 push eax ; push tmp2; (8)
.text:00401043 mov ecx, [ebp+var_4]
.text:00401046 sub ecx, 1
.text:00401049 mov [ebp+var_4], ecx ; --i; (之后i = 7)
.text:0040104C mov edx, [ebp+var_4]
.text:0040104F push edx ; push i; (7)
.text:00401050 mov eax, [ebp+var_4]
.text:00401053 add eax, 1
.text:00401056 mov [ebp+var_4], eax ; ++i; (之后i=8)
.text:00401059 mov ecx, [ebp+var_4]
.text:0040105C push ecx ; push i; (8)
.text:0040105D push offset ??_C@_0N@KFBM@?$CFd?0?$CFd?0?$CFd?0?$CFd?6?$AA@ ; "%d,%d,%d,%d\n"
.text:00401062 mov edx, [ebp+var_4]
.text:00401065 add edx, 1
.text:00401068 mov [ebp+var_4], edx ; i++; (之后i=9)
.text:0040106B mov eax, [ebp+var_4]
.text:0040106E sub eax, 1
.text:00401071 mov [ebp+var_4], eax ; i--; (之后i=8)
.text:00401074 call printf
.text:00401079 add esp, 14h
int i = 8;
int tmp1 = i;
int tmp2 = i; //临时变量 tmp1 和 tmp2 是实际上存在!
--i;
int tmp3 = i; //临时变量 tmp3 和 tmp4 是实际上不存在。本质是push i。
++i;
int tmp4 = i;
i++; //此处是处理语句中的两个后置操作符。
i--;
printf("%d,%d,%d,%d\n", tmp4, tmp3, tmp2, tmp1);
int i = 8; printf ( "%d,%d,%d,%d\n", ++i, --i, i++, i-- );
|
||
VC6.0 |
VS2005 |
TC2.0 |
tmp1 = i push tmp1 tmp2 = i push tmp2 --i push i ++i push i i++ i--
|
tmp1 = i i— tmp2 = i i++ --i ++i push tmp1 push tmp2 push i push i |
tmp1 = i i— push tmp1 tmp2 = i i++ push tmp2 --i push i ++i push i |
8,7,8,8 |
8,8,7,8 |
8,7,7,8 |