解答printf("%d,%d,%d,%d\n", ++i, --i, i++, i--)

 

转自: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--);
}
但是结果为(8 7 8 8)无论是从左到右顺序求值还是从右到左顺序求值都不应该是这个结果吧?
我觉得从左到右应该是(9 8 8 9 )从右到左是(8 7 7 8),
是我的错还是编译器的原因?如果是从右到左顺序求值,为什么结果不是(8 7 7 8)而是(8 7 8 8)
请大家指点一下!




解答

在这里我使用了VS.NET2005编译的结果是:8,8,7,8
用TC2.0编译的结果是:8,7,7,8
这里我们可以看到,由不同的编译器产生了不同结果,可见这个问题是依赖编译器的理解和实现的。
换句话说,对于i++和++i的处理本来就是非常具有歧义的,不要写有歧义的代码。

  不同编译器产生不同的结果的本质原因在于:入栈的顺序关系。
  编译器对++i和i++的处理过程都是一样的:前置的运算符一般在执行本语句之前就执行,因此无需缓存值,而后置运算符通常的处理则通常需要缓存值。

-----------------------------------------------------------------------------------------------

下面分析一下不同编译器究竟如何理解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处理过程——先处理完所有自增自减之后再最后统一一次性的入栈:

1、先处理i++和++i
i--: 缓存8,i=7
i++:缓存7,i=8
--i:i=7
++i:i=8(到这一步时i=8,也就是说最后入栈之前i的最终值为8,因此++i和--i的入栈值都是8
2、最后入栈
第一个入栈值8,第二个入栈值7,第三个入栈值8(此时的i值),第四个入栈8(此时的i值)
所以这时候栈的数据是:8,8,7,8.(从左到右)。
所以打印结果是:8,8,7,8。

i++(i--):先缓存i的原始值,然后i自增(自减)。最后入栈时,用缓存的i的原始值入栈。
++i(--i):i自增,不缓存i的原始值。最后入栈时,是更新后的i。

---------------------------------------------------------------------------------------------------------
我们再看在TC2.0下的反汇编代码:

----------------------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寄存器马上入栈

i--: 入栈8,i=7
i++:入栈7,i=8
--i:i=7,入栈7
++i:i=8,入栈8
所以导致栈里面的参数是8,7,7,8
所以打印结果是8,7,7,8.

 

  【总结】

  由于本问题中是对同一个变量多次使用++,--运算符,且对其入栈,因此运算符和入栈之间的顺序对结果有着关键影响。通过下表可以进行直观的对比:

 

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 语句之间的顺序不同,因此导致结果不同。

posted on 2012-03-01 13:52  jiajia牛  阅读(5289)  评论(0编辑  收藏  举报

导航