这是一个很简单的程序
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.

转自:http://www.cnblogs.com/hoodlum1980/archive/2008/02/25/1079983.html

转载于:https://www.cnblogs.com/hnrainll/archive/2011/05/01/2033828.html

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

  1. FreeRtos 获取任务运行时状态以及实现方式分析

    一.获取全部任务的状态 1.1 系统API函数 void vTaskList( char * pcWriteBuffer ) 1.2 函数功能 返回所有任务的: 任务名 任务当前运行状态 任务优先级 ...

  2. effective java 第2条:遇到多个构造器参数时要考虑用构造器

    如果构建一个对象需要很多参数时,那么有三种方式可以实现. 第一种就是多重构造器 public class Person {private final int id; //requiredprivate ...

  3. 使用 C++0x 时 make_shared 完美转发构造函数参数的测试编译器

    使用 C++0x 时 make_shared 完美转发构造函数参数的测试编译器 实现功能 C++实现代码 实现功能 使用 C++0x 时 make_shared 完美转发构造函数参数的测试编译器 C+ ...

  4. 怎么证明未显式定义构造方法时,编译器会自动生成无参的构造方法?

    在和类和对象篇提到: 当没有显式定义构造方法,编译器会自动提供一个没有参数的构造方法 若类中显式定义了构造方法,则默认的无参构造不再产生 本篇博客就带大家一起来验证~ 首先,显式定义构造方法:即 自己 ...

  5. 数组作为函数参数时,最好将数组大小也作为一个函数参数

    1.一个好的习惯:当数组作为函数参数时,最好将数组大小也作为函数的一个参数. 2.如果数组大小不确定,再加上可能代码写的不严谨,可能导致数组溢出(数组越界),然而编译器也不报错的情况,比如下面这个例子 ...

  6. 解决JS在url中传递参数时参数包含中文乱码的问题

    解决JS在url中传递参数时参数包含中文乱码的问题 参考文章: (1)解决JS在url中传递参数时参数包含中文乱码的问题 (2)https://www.cnblogs.com/xushengguan/ ...

  7. MyBatis中传递数组参数和List参数时if-test判空和判断长度的写法

    场景 前端传递一个部门id的数组作为查询条件查询部门id在这个数组中的数据. 在MyBatis的xml中获取到了这个数组参数后怎样进行if-test的判空与长度判断. 注: 博客: https://b ...

  8. ASP.NET在访问Controller的方法带参数时怎样防止黑客攻击

    场景 ASP.NET中MVC添加Controller以及访问其Action: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/106 ...

  9. 遇到多个构造器参数时要考虑使用Builder

    1.静态工厂和构造器有个共同的局限性:它们都不能很好地扩展到大量的可选参数.比如用一个类表示包装食品外面显示的营养成分标签.这些标签中有几个域是必需的:每份的含量.每罐的含量以及每份的卡路里.还有超过 ...

最新文章

  1. 黑马vue---13、事件修饰符的介绍
  2. 一级计算机word试题及答案,计算机一级考试试题及答案Word文档..doc
  3. java自动推断类型_推断:Facebook的新Java静态分析工具
  4. Apollo进阶课程㊱丨Apollo ROS深入介绍
  5. 内存问题提醒!结构体+protobuf做协议体发送!序列化
  6. 该放弃正在堕落的“RNN和LSTM”了
  7. java 内存情况_java查看jvm内存使用情况
  8. python爬取b站搜索结果播放地址_如何利用Python快速爬取B站全站视频信息
  9. Flutter进阶—解析动画
  10. windowsXP消灭蓝屏的另类招式
  11. cf显示网络连接服务器失败怎么办,cf连接服务器失败怎么办
  12. Docker-in-Docker: Jenkins CI 内部如何运行 docker
  13. WSUS无法发现客户端
  14. android跳转到rn界面,第五章 RN与Native—由原生页面跳转到Rn页面;在Rn页面调用Android Native组件和Native数据...
  15. F5 微信银行解决方案
  16. Could not autowire. No beans of xxx 解决办法
  17. 名帖222 赵孟頫 行书《行书三段卷》
  18. 游戏蓝牙耳机哪款好用?低延迟游戏蓝牙耳机推荐
  19. 【操作说明】新版网络穿透/动态组网/远程运维/视频拉转推设备EasyNTS上云网关配置手册介绍
  20. Spring——Spring整合MyBatis

热门文章

  1. bzoj 4260 REBXOR —— Trie树
  2. 史上最全的ASP.NET MVC路由配置,以后RouteConfig再弄不懂神仙都难救你啦~
  3. console对象及js函数
  4. VXLAN 概念(Part II)- 每天5分钟玩转 OpenStack(109)
  5. 修改一个CGRect的值
  6. SQLSERVER:sqlserver2008r2安装好后,自动提示功能不可以使用
  7. Android中Scrollview、ViewPager冲突问题汇总(已解决)
  8. MySQL临时表(转)
  9. F# 与 WPF 之多语言实现MVVM(二)
  10. 使用基于列表的表单控件