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

原创 2002年06月27日 09:56:00
一个无数人讨论过的问题,今天终于看到一个人讲得全面而清楚。下面这个帖子是
-----------------------
首先声明,简单的比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。


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

如果自增运算表达式的结果没有被使用,而仅仅简单的用于增加一员操作数,答案是明确的,前缀法和后缀法没有任何区别,编译器的处理都应该是相同的,很难想象得出有什么编译器实现可以别出心裁在二者之间制造任何差异。
测试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;	.scl	2;	.type	32;	.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;	.scl	2;	.type	32;	.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;	.scl	2;	.type	32;	.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;	.scl	2;	.type	32;	.endef
_test1__Fi:
	pushl %ebp
	movl %esp,%ebp
	movl 8(%ebp),%eax
	leave
	ret
	.align 4
.globl _test2__Fi
	.def	_test2__Fi;	.scl	2;	.type	32;	.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;	.scl	2;	.type	32;	.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;	.scl	2;	.type	32;	.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上。

c语言中i++与++i的区别及运行效率

在c语言我们会经常把i++与++i弄混淆。 i++    是先使用,再自加(其自加是在遇到结束标志时才会进行) 即a = i;i=i+1; ++i    是先自加,再使用 即 i=i+1; a...
  • Mormont
  • Mormont
  • 2016年11月11日 19:39
  • 4344

浅谈java之++i和i++区别

浅谈java之++i和i++区别                   今天简单谈谈关于java的一个误区,相信很多刚开始学习java的朋友都会遇到这个问题,虽然问题很简单,但是经常容易搞混,说说jav...
  • u011747761
  • u011747761
  • 2015年01月09日 16:31
  • 10375

C语言i++和++i的区别

i++和++i的区别虽然简单,还是记录一下吧!     【知识点】      1. 对于普通独立的语句,i++和++i是一样的,如:         i++; 等效于i=i+1;         +...
  • u013046097
  • u013046097
  • 2016年12月03日 11:09
  • 1753

C++中的++i和i++

最近虽然比较忙,也有点压力,不过相对还是闲的时间多,花了些时间看了些书。 还是感觉要写点什么,就从这里开始吧。 写在前面:--i和i-- 与 ++i和i++ 相似,这里仅讨论后者。 一、对于内...
  • qq_35154908
  • qq_35154908
  • 2016年10月19日 10:52
  • 738

Java中i++和++i的区别

说来惭愧,从事开发工作也有一年时间了,然而在今天的一个业务逻辑里突然发现原来我对i++和++i都没有理解,或者说我之前的理解是错误的。这对于一个有追求的程序猿是不能容忍的。知道之后,迅速恶补学习,现在...
  • qq_34471736
  • qq_34471736
  • 2017年01月18日 11:41
  • 3366

++i和i++效率谁高

2014届搜狗校招笔试题再次提到了这个经典的问题,去百度上查资料解答是: (1):++i是在i上直接加1,表达式的值是i本身 i++也是在i上加1,表达式的值是加1前的副本 因为要存副本,所...
  • liuhuanjun222
  • liuhuanjun222
  • 2015年04月10日 20:14
  • 223

++i 与i++哪个效率更高?

解析: 在这里声明,简单的比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。首先考虑内建数据类型的情况:如果自增运算表达式的结果没有被使用,而是仅仅简单的用于增加...
  • u012129558
  • u012129558
  • 2016年09月05日 10:23
  • 329

++i和i++效率谁高

2014届搜狗校招笔试题再次提到了这个经典的问题,去百度上查资料解答是: (1):++i是在i上直接加1,表达式的值是i本身 i++也是在i上加1,表达式的值是加1前的副本 因为要存副本,所以效率略...
  • w15234800067
  • w15234800067
  • 2013年12月14日 09:53
  • 446

++i和i++效率谁高

2014届搜狗校招笔试题再次提到了这个经典的问题,去百度上查资料解答是: (1):++i是在i上直接加1,表达式的值是i本身 i++也是在i上加1,表达式的值是加1前的副本 因为要存副本,所...
  • big_kevin
  • big_kevin
  • 2016年11月30日 14:02
  • 60

i++ 与 ++i 的效率哪个更高?

1、http://hi.baidu.com/keeptry/item/851a943815af4049033edc95 2、http://bbs.csdn.net/topics/250027391 ...
  • leepwang
  • leepwang
  • 2013年10月22日 23:24
  • 1479
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:i++和++i的效率差别
举报原因:
原因补充:

(最多只允许输入30个字)