欢迎 chencm 加入本站!
 免费注册  用户登陆  汇款方式  汇款确认  产品报价  联系我们  帮助中心
加入收藏
设为首页
会员体系
申请VIP
网站首页 光盘超市 软件下载 技术文章 专题 用户中心 VIP会员 技术论坛 网站留言 娱乐中心 卓越资源
今天是:2009年01月06日 星期二  您现在位于: 首页 → 技术文章 → i++和++i的效率差...
   i++和++i的效率差别
作者:  出处:Unix爱好者家园unix-cd.com  更新时间: 2005年11月22日 
一个无数人讨论过的问题,今天终于看到一个人讲得全面而清楚。首先声明,简单的比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。  

首先考虑内建数据类型的情况: 
如果自增运算表达式的结果没有被使用,而仅仅简单的用于增加一员操作数,答案是明确的,前缀法和后缀法没有任何区别,编译器的处理都应该是相同的,很难想象得出有什么编译器实现可以别出心裁在二者之间制造任何差异。 
测试C++源代码如下: 
//test1.cpp 
void test() 

int i=0; 
i++; 
++i; 

Gnu C/C++ 2编译的汇编中间代码如下: 
.file "test1.cpp" 
gcc2_compiled.: 
___gnu_compiled_cplusplus: 
.text 
.align 4 
.globl _test__Fv 
.def _test__Fv; .scl 2; .type 32; .endef 
_test__Fv: 
pushl ëp 
movl %esp,ëp 
subl $24,%esp 
movl $0,-4(ëp) i=0 
incl -4(ëp) i++ 
incl -4(ëp) ++i 
jmp L3 
jmp L2 
.p2align 4,,7 
L3: 
L2: 
leave 
ret 
很显然,不管是i++还是++i都仅仅是一条incl指令而已。 
如果表达式的结果被使用,那么情况要稍微复杂一些。 
测试C++源代码如下: 
//test2.cpp 
void test() 

int i=0,a,b; 
a=i++; 
b=++i; 

Gnu C/C++ 2编译的汇编中间代码如下: 
.file "test2.cpp" 
gcc2_compiled.: 
___gnu_compiled_cplusplus: 
.text 
.align 4 
.globl _test__Fv 
.def _test__Fv; .scl 2; .type 32; .endef 
_test__Fv: 
pushl ëp 
movl %esp,ëp 
subl $24,%esp 
movl $0,-4(ëp) i=0 
movl -4(ëp),êx i --> ax 
movl êx,-8(ëp) ax --> a(a=i) 
incl -4(ëp) i++ 
incl -4(ëp) ++i 
movl -4(ëp),êx i --> ax 
movl êx,-12(ëp) ax --> b(b=i) 
jmp L3 
jmp L2 
.p2align 4,,7 
L3: 
L2: 
leave 
ret 
有差别吗?显然也没有,同样是一条incl指令,再加上两条movl指令借用eax寄存器复制调用栈内容。 
让我们再加上编译器优化,重新编译后的汇编代码如下: 
.file "test2.cpp" 
gcc2_compiled.: 
___gnu_compiled_cplusplus: 
.text 
.align 4 
.globl _test__Fv 
.def _test__Fv; .scl 2; .type 32; .endef 
_test__Fv: 
pushl ëp 
movl %esp,ëp 
leave 
ret 
好了,优化的过火了,由于i,a,b三个变量没有被使用,所以干脆全都被优化了,结果成了一个什么都不做的空函数体。 
那么,让我们再加上一点代码使用a和b的结果吧,这样i的结果也不能够忽略了,C++源代码如下: 
//test3.cpp 
int test() 

int i=0,a,b; 
a=i++; 
b=++i; 
return a+b; 

此时汇编代码如下: 
.file "test3.cpp" 
gcc2_compiled.: 
___gnu_compiled_cplusplus: 
.text 
.align 4 
.globl _test__Fv 
.def _test__Fv; .scl 2; .type 32; .endef 
_test__Fv: 
pushl ëp 
movl %esp,ëp 
movl $2,êx 
leave 
ret 
你还是没有想到吧,答案仅仅是编译器计算了返回值,常量展开(constant-unwinding)启动,变成了直接返回常量结果。 
怎么办?我们把i变成参数,避免这种预期以外的结果,C++源代码如下: 
//test4.cpp 
int test1(int i) 

int a=i++; 
return a; 

int test2(int i) 

int a=++i; 
return a; 

好了,很辛苦,终于得到了不一样的汇编代码: 
.file "test4.cpp" 
gcc2_compiled.: 
___gnu_compiled_cplusplus: 
.text 
.align 4 
.globl _test1__Fi 
.def _test1__Fi; .scl 2; .type 32; .endef 

_test1__Fi: 
pushl ëp 
movl %esp,ëp 
movl 8(ëp),êx 
leave 
ret 
.align 4 
.globl _test2__Fi 
.def _test2__Fi; .scl 2; .type 32; .endef 
_test2__Fi: 
pushl ëp 
movl %esp,ëp 
movl 8(ëp),êx 
incl êx 
leave 
ret 
和你接触到的教条正相反吧,++i反而增加了一条汇编指令incl,而i++却没有,这就是编译器优化的魅力。 
因为不管i有没有增加,都不影响a的值,而函数仅仅返回i的值,所以i的自增运算就根本不必进行了。 
所以,为了更客观一些,我们将i参数改为按照引用传递,C++源代码如下; 
//test5.cpp 
int test1(int &i) 

int a=i++; 
return a; 

int test2(int &i) 

int a=++i; 
return a; 

这一次的结果加入了指针的运算,稍微复杂一些: 
.file "test5.cpp" 
gcc2_compiled.: 
___gnu_compiled_cplusplus: 
.text 
.align 4 
.globl _test1__FRi 
.def _test1__FRi; .scl 2; .type 32; .endef 
_test1__FRi: 
pushl ëp 
movl %esp,ëp 
movl 8(ëp),êx 
movl (êx),íx 
incl (êx) 
movl íx,êx 
leave 
ret 
.align 4 
.globl _test2__FRi 
.def _test2__FRi; .scl 2; .type 32; .endef 
_test2__FRi: 
pushl ëp 
movl %esp,ëp 
movl 8(ëp),êx 
movl (êx),íx 
leal 1(íx),ìx 
movl ìx,(êx) 
movl ìx,êx 
leave 
ret 
惊讶吗?还是a=i++的代码更高效一些,不知道这会让你有什么想法。反正,我得出的结论,对于内建数据类型来说,i++和++i孰优孰劣,是编译器实现相关的,实在不必太可以关心这个问题。 

最后让我们再回到起点,对于自定义数据类型(主要是指类)说,不需要再做很多汇编代码的分析了,我很清楚的知道为什么会有人循循善诱。 
因为前缀式可以返回对象的引用,而后缀式必须返回对象的值,所以导致了在大对象的时候产生了较大的复制开销,引起效率降低,因此会有劝告尽量使用前缀式,尽可能避免后缀式,除非从行为上真的需要后缀式。 
这也就是More Effective C++/Term 7中的原文提到的,处理使用者自定义类型(注意不是指内建类型)的时候,应该尽可能的使用前缀式地增/递减,因为他天生体质较佳。 
同时,为了保证前缀和后缀对递增/递减的语义的实现保持一致,设计上的一般原则是后缀式的实现以前缀式为基础,这样,后缀式往往多了一次函数调用,这也许也是一个需要考虑的效率因素,不过相比之下,就有点微乎其微了。 
 (本文已被浏览 1515 次)
 发布人:sdccf
 → 推荐给我的好友
上篇文章:Linux系统中Mysql 的安装备份与密码恢复
下篇文章:Linux下驱动SD/MMC读卡器
 相关文章:
SCO OpenServer 5.0.7连上SQLSERVER2000全纪录 SCO OpenServer 5.0.7制作紧急启动光盘的方法
将SCO OpenServer 5.0.6安装到IBM X3650上 如何把一个软盘镜像HBA文件转光盘ISO格式HBA文件
SCO操作系统安装Serial ATA (SATA)硬盘的方式 ubuntu 8.04 上安装 oracle 10g
sco unix 5.07 使用windows FAT 格式的USB盘过程 制作带有hpsas驱动的SCO5.0.6应急和引导光盘安装G5
联想扬天M2600C安装SCO5.0.6+Windows XP 万年历的C语言程序
Sco UNIX的核心引导过程详解 Fedora 7一共有3种类型的Live镜像
Fedora 7下玩游戏 优化Fedora 7,关掉不需要的Fedora services
Linux 下 C 语言编程 C语言常见错误
在centos 5中使用xfce桌面环境 C语言中用qsort()快速排序
不使用逻辑运算求得两数的最大值 C语言中用bsearch()实现查找操作

相关搜索
查看百度中关于i++和++i的效率差别的更多内容
查看google中关于i++和++i的效率差别的更多内容
   文章分类
操作系统 |
SCO_UNIX  Sun_Solaris  IBM_AIX  HP_UX  Linux  BSD  Tru64_UNIX 
通用UNIX知识  Windows  Minix 
程序设计 |
Shell编程  C/C++  汇编  PHP  JAVA  Perl  Python 
ASP/HTML  XML  中间件 
数据库 |
Oracle  Informix  Sybase  Fox  DB2  SQL  MySQL 
PostgreSQL 
网络应用 |
网络应用 
计算机硬件 |
计算机主机  打印机  路由器  交换机  终端  磁带机  MO 
刻录机  终端服务器  调制解调器 
   文章评论
  → 评论内容 (点击查看)   共0条评论,每页显示5条评论   浏览所有评论
(没有相关评论)
  → 发表我的评论
您的姓名: 您的Email:
评论内容:
250字内
发表评论:      发表评论须知 →
  • 尊重网上道德,遵守《全国人大常委会关于维护互联网安全的决定》及中华人民共和国其他各项有关法律法;
  • 本站有权保留或删除您发表的任何评论内容;
  • 关于我们 ┋  网站留言 ┋  网站地图 ┋  友情链接 ┋  与我在线 ┋  汇款确认 ┋  管理 ┋  TOP
    Unix爱好者家园  http://www.unix-cd.com/
    联系我们:sdccf@163.com
    腾讯QQ: 7644599
    备案序号:鲁ICP备05000455号
    Copyright (c) 2001-2008 Unix-cd.com. All Rights Reserved.