i++和++i作为参数时的编译器处理方式分析

lingyin55 2009-02-19 10:22:28
发个帖,大家共同学习一下。

这是一个很简单的程序
void main()
{
int i=8;
printf("%d,%d,%d,%d\n", ++i, --i, i++, i--);
}

在这里我使用了VS.NET2005编译的结果是:8,8,7,8
用TC2.0编译的结果是:8,7,7,8
VC6.0我没有安装,所以没有试过,也没办法分析。
这里我们可以看到,由不同的编译器产生了不同结果,可见这个问题是依赖编译器的理解和实现的。换句话说,对于i++和++i的处理本来就是非常具有歧义的,当然在自己应用中我相信也不会有任何程序员写出这样歧义的代码。但是作为一个问题,我们有必要分析一下不同编译器究竟如何理解i++和++i操作符的。
我们在学习C的时候,应该已经大概知道了i++和++i两者的区别,即“++”符号在i之前还是之后,决定了i自增操作和他的语句的执行顺序的关系。即i++,理解为i在其语句中取原始值,++i在其语句中取自增后的新值。这一点是毫无疑义的。但是问题在于,网友的问题中又涉及到了i++,++i在作为参数时候的处理,所以这时候我们就会感到困惑,i++和++i在作为参数的时候,和进入堆栈的顺序之间有何关系呢?根据前面的实验,可见TC2.0和VS.net2005的处理不同,可见两者对其处理不同,那么造成这种不同的结果的原因是什么呢?我们从代码上无法看到差异,因此我们必须看汇编语言才能知晓,编译器到底把我们的代码翻译成了什么墨阳。下面我采用IDA反汇编编译器把生成的.exe文件:由于程序很小,所以我们很容易找到相应的汇编代码(TC2.0的程序中有很多中断的相关代码,更加难以找到),结果如下:
VS.NET 2005的代码
mov [ebp+var_8], 8 //i=8
.text:004113E5 mov eax, [ebp+var_8] //
.text:004113E8 mov [ebp+var_D0], eax //i--之前把i的值保存到,temp[0]=8
.text:004113EE mov ecx, [ebp+var_8]
.text:004113F1 sub ecx, 1 //i--,(从右向左数第一个参数)
.text:004113F4 mov [ebp+var_8], ecx //i=7
.text:004113F7 mov edx, [ebp+var_8]
.text:004113FA mov [ebp+var_D4], edx //i++之前把i保存到:temp[1]=7 (这时候已经执行过i--了)
.text:00411400 mov eax, [ebp+var_8] //i++,(从右向左数第二个参数)
.text:00411403 add eax, 1
.text:00411406 mov [ebp+var_8], eax //i=8
.text:00411409 mov ecx, [ebp+var_8]
.text:0041140C sub ecx, 1 //--i, 无需保存修改前的值,直接改变实参i
.text:0041140F mov [ebp+var_8], ecx //i=7
.text:00411412 mov edx, [ebp+var_8]
.text:00411415 add edx, 1 //++i,
.text:00411418 mov [ebp+var_8], edx //i=8
.text:0041141B mov esi, esp
.text:0041141D mov eax, [ebp+var_D0] //压栈temp[0]=8
.text:00411423 push eax
.text:00411424 mov ecx, [ebp+var_D4] //压栈temp[1]=7
.text:0041142A push ecx
.text:0041142B mov edx, [ebp+var_8] //压栈i=8
.text:0041142E push edx
.text:0041142F mov eax, [ebp+var_8] //压栈i=8
.text:00411432 push eax
.text:00411433 push offset aDDDD ; "%d,%d,%d,%d\n" //压栈字符串"%d,%d,%d,%d\n"的地址
.text:00411438 call ds:printf //调用打印函数,输出8,8,7,8
.text:0041143E add esp, 14h
.text:00411441 cmp esi, esp
.text:00411443 call sub_41113B
.text:00411448 mov esi, esp
可见,++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下的反汇编代码:
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

----------------------------------------------------------------------------------------------------------------------
下面我们将总结和分析两种编译器的处理之间有何不同:@@@@@@

请注意两者的区别主要是,他们是一边处理自增自减并一边随时入栈,还是先处理完所有自增自减之后再最后统一一次性的入栈。
(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.

...全文
366 8 打赏 收藏 转发到动态 举报
写回复
用AI写文章
8 条回复
切换为时间正序
请发表友善的回复…
发表回复
hnrainll 2011-05-01
  • 打赏
  • 举报
回复
很好,学习到了.
zhuweiping2003 2009-07-09
  • 打赏
  • 举报
回复
mark
lingyin55 2009-05-16
  • 打赏
  • 举报
回复
当时没什么分啊。。

[Quote=引用 4 楼 mengde007 的回复:]
原来你也发0分帖
[/Quote]
mengde007 2009-05-14
  • 打赏
  • 举报
回复
原来你也发0分帖
老邓 2009-05-14
  • 打赏
  • 举报
回复
哦,原来已经讨论过了。
liuda129 2009-05-12
  • 打赏
  • 举报
回复
似懂非懂!不过还是有收获
fairchild811 2009-02-19
  • 打赏
  • 举报
回复
复杂啊,各编译器不一样

64,656

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧