新书推介:《语义网技术体系》
作者:瞿裕忠,胡伟,程龚
   XML论坛     W3CHINA.ORG讨论区     计算机科学论坛     SOAChina论坛     Blog     开放翻译计划     新浪微博  
 
  • 首页
  • 登录
  • 注册
  • 软件下载
  • 资料下载
  • 核心成员
  • 帮助
  •   Add to Google

    >> 本版讨论高级C/C++编程、代码重构(Refactoring)、极限编程(XP)、泛型编程等话题
    [返回] 计算机科学论坛计算机技术与应用『 C/C++编程思想 』 → i++和++i的效率差别 查看新帖用户列表

      发表一个新主题  发表一个新投票  回复主题  (订阅本版) 您是本帖的第 2800 个阅读者浏览上一篇主题  刷新本主题   树形显示贴子 浏览下一篇主题
     * 贴子主题: i++和++i的效率差别 举报  打印  推荐  IE收藏夹 
       本主题类别:     
     卷积内核 帅哥哟,离线,有人找我吗?
      
      
      威望:8
      头衔:总统
      等级:博士二年级(版主)
      文章:3942
      积分:27590
      门派:XML.ORG.CN
      注册:2004/7/21

    姓名:(无权查看)
    城市:(无权查看)
    院校:(无权查看)
    给卷积内核发送一个短消息 把卷积内核加入好友 查看卷积内核的个人资料 搜索卷积内核在『 C/C++编程思想 』的所有贴子 访问卷积内核的主页 引用回复这个贴子 回复这个贴子 查看卷积内核的博客楼主
    发贴心情 i++和++i的效率差别

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


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

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


       收藏   分享  
    顶(0)
      




    ----------------------------------------------
    事业是国家的,荣誉是单位的,成绩是领导的,工资是老婆的,财产是孩子的,错误是自己的。

    点击查看用户来源及管理<br>发贴IP:*.*.*.* 2007/7/28 9:04:00
     
     GoogleAdSense
      
      
      等级:大一新生
      文章:1
      积分:50
      门派:无门无派
      院校:未填写
      注册:2007-01-01
    给Google AdSense发送一个短消息 把Google AdSense加入好友 查看Google AdSense的个人资料 搜索Google AdSense在『 C/C++编程思想 』的所有贴子 访问Google AdSense的主页 引用回复这个贴子 回复这个贴子 查看Google AdSense的博客广告
    2024/11/26 14:54:43

    本主题贴数1,分页: [1]

    管理选项修改tag | 锁定 | 解锁 | 提升 | 删除 | 移动 | 固顶 | 总固顶 | 奖励 | 惩罚 | 发布公告
    W3C Contributing Supporter! W 3 C h i n a ( since 2003 ) 旗 下 站 点
    苏ICP备05006046号《全国人大常委会关于维护互联网安全的决定》《计算机信息网络国际联网安全保护管理办法》
    62.500ms