分享
 
 
 

i++和++i的效率差别

王朝other·作者佚名  2006-01-08
窄屏简体版  字體: |||超大  

一个无数人讨论过的问题,今天终于看到一个人讲得全面而清楚。下面这个帖子是shornmao (死猫)发的,我只是帮他贴过来而已,希望死猫不会生我的气。

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

首先声明,简单的比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。

首先考虑内建数据类型的情况:

如果自增运算表达式的结果没有被使用,而仅仅简单的用于增加一员操作数,答案是明确的,前缀法和后缀法没有任何区别,编译器的处理都应该是相同的,很难想象得出有什么编译器实现可以别出心裁在二者之间制造任何差异。

测试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 %ebp

movl %esp,%ebp

subl $24,%esp

movl $0,-4(%ebp);i=0

incl -4(%ebp);i++

incl -4(%ebp);++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;.scl2;.type32;.endef

_test__Fv:

pushl %ebp

movl %esp,%ebp

subl $24,%esp

movl $0,-4(%ebp);i=0

movl -4(%ebp),%eax;i --> ax

movl %eax,-8(%ebp);ax --> a(a=i)

incl -4(%ebp);i++

incl -4(%ebp);++i

movl -4(%ebp),%eax;i --> ax

movl %eax,-12(%ebp);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;.scl2;.type32;.endef

_test__Fv:

pushl %ebp

movl %esp,%ebp

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;.scl2;.type32;.endef

_test__Fv:

pushl %ebp

movl %esp,%ebp

movl $2,%eax

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;.scl2;.type32;.endef

_test1__Fi:

pushl %ebp

movl %esp,%ebp

movl 8(%ebp),%eax

leave

ret

.align 4

.globl _test2__Fi

.def_test2__Fi;.scl2;.type32;.endef

_test2__Fi:

pushl %ebp

movl %esp,%ebp

movl 8(%ebp),%eax

incl %eax

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;.scl2;.type32;.endef

_test1__FRi:

pushl %ebp

movl %esp,%ebp

movl 8(%ebp),%eax

movl (%eax),%edx

incl (%eax)

movl %edx,%eax

leave

ret

.align 4

.globl _test2__FRi

.def_test2__FRi;.scl2;.type32;.endef

_test2__FRi:

pushl %ebp

movl %esp,%ebp

movl 8(%ebp),%eax

movl (%eax),%edx

leal 1(%edx),%ecx

movl %ecx,(%eax)

movl %ecx,%eax

leave

ret

惊讶吗?还是a=i++的代码更高效一些,不知道这会让你有什么想法。反正,我得出的结论,对于内建数据类型来说,i++和++i孰优孰劣,是编译器实现相关的,实在不必太可以关心这个问题。

最后让我们再回到起点,对于自定义数据类型(主要是指类)说,不需要再做很多汇编代码的分析了,我很清楚的知道为什么会有人循循善诱。

因为前缀式可以返回对象的引用,而后缀式必须返回对象的值,所以导致了在大对象的时候产生了较大的复制开销,引起效率降低,因此会有劝告尽量使用前缀式,尽可能避免后缀式,除非从行为上真的需要后缀式。

这也就是More Effective C++/Term 7中的原文提到的,处理使用者自定义类型(注意不是指内建类型)的时候,应该尽可能的使用前缀式地增/递减,因为他天生体质较佳。

同时,为了保证前缀和后缀对递增/递减的语义的实现保持一致,设计上的一般原则是后缀式的实现以前缀式为基础,这样,后缀式往往多了一次函数调用,这也许也是一个需要考虑的效率因素,不过相比之下,就有点微乎其微了。

重申一点关于这个问题的进一步叙述,可以在Scott Mayer的<<More Effective C++>>一书的条款7中获得,大约在原书的P31-34上。

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有