转自:http://www.cnblogs.com/hoodlum1980/archive/2008/02/25/1079983.html
对原文有较大的修改,如有冒犯之处还请及时通知。
问题
void main()
{
int i=8;
printf("%d,%d,%d,%d\n", ++i, --i, i++, i--);
}
请大家指点一下!
解答
换句话说,对于i++和++i的处理本来就是非常具有歧义的,不要写有歧义的代码。在这里我使用了VS.NET2005编译的结果是:8,8,7,8
用TC2.0编译的结果是:8,7,7,8这里我们可以看到,由不同的编译器产生了不同结果,可见这个问题是依赖编译器的理解和实现的。
下面分析一下不同编译器究竟如何理解i++和++i操作符和入栈之间的顺序关系。
VS.net2005反编译结果如下:
1 mov [ebp+var_8], 8 //i=8
2 .text:004113E5 mov eax, [ebp+var_8] //
3 .text:004113E8 mov [ebp+var_D0], eax //i--之前把i的值保存到,temp[0]=8
4 .text:004113EE mov ecx, [ebp+var_8]
5 .text:004113F1 sub ecx, 1 //i--,(从右向左数第一个参数)
6 .text:004113F4 mov [ebp+var_8], ecx //i=7
7 .text:004113F7 mov edx, [ebp+var_8]
8 .text:004113FA mov [ebp+var_D4], edx //i++之前把i保存到:temp[1]=7 (这时候已经执行过i--了)
9 .text:00411400 mov eax, [ebp+var_8] //i++,(从右向左数第二个参数)
10 .text:00411403 add eax, 1
11 .text:00411406 mov [ebp+var_8], eax //i=8
12 .text:00411409 mov ecx, [ebp+var_8]
13 .text:0041140C sub ecx, 1 //--i, 无需保存修改前的值,直接改变实参i
14 .text:0041140F mov [ebp+var_8], ecx //i=7
15 .text:00411412 mov edx, [ebp+var_8]
16 .text:00411415 add edx, 1 //++i,
17 .text:00411418 mov [ebp+var_8], edx //i=8
18 .text:0041141B mov esi, esp
19 .text:0041141D mov eax, [ebp+var_D0] //压栈temp[0]=8
20 .text:00411423 push eax
21 .text:00411424 mov ecx, [ebp+var_D4] //压栈temp[1]=7
22 .text:0041142A push ecx
23 .text:0041142B mov edx, [ebp+var_8] //压栈i=8
24 .text:0041142E push edx
25 .text:0041142F mov eax, [ebp+var_8] //压栈i=8
26 .text:00411432 push eax
27 .text:00411433 push offset aDDDD ; "%d,%d,%d,%d\n" //压栈字符串"%d,%d,%d,%d\n"的地址
28 .text:00411438 call ds:printf //调用打印函数,输出8,8,7,8
29 .text:0041143E add esp, 14h
30 .text:00411441 cmp esi, esp
31 .text:00411443 call sub_41113B
32 .text:00411448 mov esi, esp
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);
VC2005.NET处理过程——先处理完所有自增自减之后再最后统一一次性的入栈:
i++:缓存7,i=8
--i:i=7
++i:i=8(到这一步时i=8,也就是说最后入栈之前i的最终值为8,因此++i和--i的入栈值都是8)
所以这时候栈的数据是:8,8,7,8.(从左到右)。
所以打印结果是:8,8,7,8。
i++(i--):先缓存i的原始值,然后i自增(自减)。最后入栈时,用缓存的i的原始值入栈。
++i(--i):i自增,不缓存i的原始值。最后入栈时,是更新后的i。
---------------------------------------------------------------------------------------------------------
我们再看在TC2.0下的反汇编代码:
0:01FA sub_1FA proc near ; CODE XREF: start+11Ap
seg000:01FA push bp //保护bp入栈
seg000:01FB mov bp, sp
seg000:01FD push si //保护si入栈
seg000:01FE mov si, 8 //i=8
seg000:0201 mov ax, si //ax=i
seg000:0203 dec si //i--,i=7
seg000:0204 push ax //ax入栈,8入栈
seg000:0205 mov ax, si //ax=i,ax=7
seg000:0207 inc si //i++,i=8
seg000:0208 push ax //ax入栈,7入栈
seg000:0209 dec si //--i,i=7
seg000:020A mov ax, si //ax=i
seg000:020C push ax //ax入栈,7入栈
seg000:020D inc si //++i,i=8
seg000:020E mov ax, si //ax=i
seg000:0210 push ax //ax入栈,8入栈
seg000:0211 mov ax, 194h
seg000:0214 push ax //194h入栈
seg000:0215 call sub_AD9
seg000:0218 add sp, 0Ah
seg000:021B call sub_15DB
seg000:021E xor ax, ax
seg000:0220 jmp short $+2
seg000:0222 pop si //恢复si
seg000:0223 pop bp //恢复bp
seg000:0224 retn
seg000:0224 sub_1FA endp
上面的行为可以翻译为下面的等效代码(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
TC处理过程——每执行一个语句,就把i通过ax寄存器马上入栈
【总结】
由于本问题中是对同一个变量多次使用++,--运算符,且对其入栈,因此运算符和入栈之间的顺序对结果有着关键影响。通过下表可以进行直观的对比:
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 |
备注:上面的 tmp1,tmp2 在 VC6和 VS2005中是栈上的实际临时变量(它们是由于两个后置运算符而产生的),在TC2.0中是使用AX寄存器中转。
仔细观察上面的表格中可以发现,push 了 4 个 i 的顺序是没有疑问的,由于有后置运算符,所以需要两个临时变量来缓存当前值。结果不同的原因在于:不同编译器,push 语句之间的顺序不同,因此导致结果不同。