文章出处: http://blog.sina.com.cn/s/blog_5ca785c30100bb89.html

websense的一道笔试题,问++i为什么比i++效率高。

有有心人详细而全面的分析,特此转来。(from BYR job版)

下面就是正文:

首先声明,简单的比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。 
 
首先考虑内建数据类型的情况:
 
如果自增运算表达式的结果没有被使用,而仅仅简单的用于增加一员操作数,答案是明确的,前缀法和后缀法没有任何区别,编译器的处理都应该是相同的,很难想象得出有什么编译器实现可以别出心裁在二者之间制造任何差异。
测试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上。

前缀式与后缀式的差别相关推荐

  1. java自动递增前缀式和后缀式区别

    java自动递增前缀式和后缀式区别 java自动递增(自动递减)前缀式表达式 '++' 操作符位于变量或表达式的前面,而后缀式表达式'++'位于变量或表达式的后面,Example: 前缀式: ++i: ...

  2. nyoj-257-郁闷的C小加(一 )中缀式变后缀式

    题目链接:here~~~~~~~ 今天看了此题,感觉栈和队列很好用,进一步深入了解 一个算术表达式,含有数字(为简化处理,数字只有一位),运算符:+.-.*,以及括号,求表达式的值. 给出的表达式是一 ...

  3. NYOJ 467 中缀式变后缀式

    中缀式变后缀式 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 人们的日常习惯是把算术表达式写成中缀式,但对于机器来说更"习惯于"后缀式,关于算术表达 ...

  4. c 语言表达式求值中缀变后缀,表达式求值关于中缀式转后缀式的问题!

    已结贴√ 问题点数:10 回复次数:6 表达式求值关于中缀式转后缀式的问题! 本人看资料的时候遇到一段代码,这段代码中转为后缀式的优先级那句不明白,请大神赐教,代码如下(c语言) #include # ...

  5. 算术表达式的前缀式、中缀式、后缀式相互转换

    中缀表达式(中缀记法) 中缀表达式是一种通用的算术或逻辑公式表示方法,操作符以中缀形式处于操作数的中间.中缀表达式是人们常用的算术表示方法. 虽然人的大脑很容易理解与分析中缀表达式,但对计算机来说中缀 ...

  6. 语法树,前缀式,中缀式,后缀式

    前序遍历:根左右 中序遍历:左根右 后序遍历:左右根 前缀式:+AB 中缀式:A+B 后缀式:AB+ 例题一: 前序遍历(前缀式):- + 4 * 1 - 5 2 / 6 3 根在前,从最后开始:-5 ...

  7. 逆波兰式(后缀式)详解

    原表达式:a*(b*(c+d/e)-f)#    /* # 为表达式结束符号*/ 后缀式:abcde/+*f-*# 为运算符定义优先级:#   (   +   -   *   /   ** -1   ...

  8. nyoj 1272 表达式求值(中缀式转后缀式)

    表达式求值 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 假设表达式定义为: 1. 一个十进制的正整数 X 是一个表达式. 2. 如果 X 和 Y 是 表达式,则 X+ ...

  9. java 中缀式转后缀式

    import java.util.Stack; import java.util.regex.*; public class StringToArithmetic { private StringTo ...

最新文章

  1. cmd文件内容添加到文件内容命令
  2. 登录form php一个页面跳转页面,Extjs4中表单登录功能、登录成功跳转页面的代码...
  3. 【设计】线框图、原型和视觉稿的区别
  4. confluence统计_【漏洞预警】confluence远程代码执行漏洞(CVE-2019-3396)
  5. 可信执行环境 TEE分类
  6. 浅析 HLS 流媒体协议
  7. 统计不及格人数(PTA-武理-C实验)
  8. Frontend Development
  9. 知名网站的 404 页面长啥样?最后一个我惊呆了!
  10. matplotlib设置坐标轴
  11. 联通沃享小颗粒真正好用的(虚拟专用网络)正式上线
  12. GBase 8s 数据库操作指南
  13. Linux基础入门之内外命令讲解篇
  14. pdm生成java_Powerdesigner逆向工程从现有数据库生成PDM
  15. 华为交换机常见的ACL操作
  16. nodejs html 生成图片,使用nodejs将html5 canvas base64编码图片保存为文件
  17. FormData+Ajax文件上传
  18. Python安装Graphviz 详细图文教程
  19. 怎么通过IP地址找MAC
  20. 基于Php的插画约稿网站

热门文章

  1. pyqt_Matplot
  2. 使用 Nginx 转发 Oracle 端口
  3. js中var和let的区别?
  4. Nuke与Natron的区别是什么?
  5. [364]尚未启动messenger服务,将不发送netsend通知
  6. IQ_Tuning_Overview
  7. mysql是关系型数据库吗_mysql属于关系型数据库吗
  8. 华为机试——简单题整合(二)(C++)
  9. Linux:解决 kill 进程失败问题
  10. BL235XX系列静电问题经验案例