前言

今天在牛客上看面经,看到一个问题:num++; num+=1; num = num +1; 哪个效率最高?

自从学习C语言开始,我就在纠结for语言应该写i++,还是++i,其实这个问题,可以通过汇编代码来看看。

区别

首先说明,自增操作符是 num = num + 1 或者 num += 1 的缩写,但又有不同,比如 C++ 中涉及到了操作符重载,其他语言又有不同的特性,但是本文只讨论最简单最经典的 C 。

赋值顺序:

  1. int m = i++; // 变量 m 被赋值为 i 后,变量 i 才自增
  2. int m = ++i; // 变量 i 自增后,变量 m 才被赋值为 i

i++ 只能作为右值,而 ++i 可以作为左右值:

  1. int *p1 = &(++i); // 正确
  2. int *p2 = &(i++); // 错误
  3. ++i = 1; // 正确
  4. i++ = 1; // 错误

i++ 不能作为左值的原因,观察其汇编可以知道,i++ 返回的只是一个临时变量,或者说只是一个存在寄存器中的值。而 ++i 返回的就是 i 本身,或者说是 i 的引用地址。

底层汇编

先来看一段代码:

  1. int main() {
  2. int i = 0;
  3. i++;
  4. ++i;
  5. i+=1;
  6. i=i+1;
  7. return 0;
  8. }

在 gcc -O0 无优化编译后的汇编代码为:

  1. a.out`main:
  2. 0x100000f70 <+0>: pushq %rbp
  3. 0x100000f71 <+1>: movq %rsp, %rbp
  4. 0x100000f74 <+4>: xorl %eax, %eax
  5. 0x100000f76 <+6>: movl $0x0, -0x4(%rbp)
  6. 0x100000f7d <+13>: movl $0x0, -0x8(%rbp)
  7. 0x100000f84 <+20>: movl -0x8(%rbp), %ecx
  8. 0x100000f87 <+23>: addl $0x1, %ecx
  9. 0x100000f8a <+26>: movl %ecx, -0x8(%rbp)
  10. 0x100000f8d <+29>: movl -0x8(%rbp), %ecx
  11. 0x100000f90 <+32>: addl $0x1, %ecx
  12. 0x100000f93 <+35>: movl %ecx, -0x8(%rbp)
  13. 0x100000f96 <+38>: movl -0x8(%rbp), %ecx
  14. 0x100000f99 <+41>: addl $0x1, %ecx
  15. 0x100000f9c <+44>: movl %ecx, -0x8(%rbp)
  16. 0x100000f9f <+47>: movl -0x8(%rbp), %ecx
  17. 0x100000fa2 <+50>: addl $0x1, %ecx
  18. 0x100000fa5 <+53>: movl %ecx, -0x8(%rbp)
  19. 0x100000fa8 <+56>: popq %rbp
  20. 0x100000fa9 <+57>: retq

可以惊讶地发现,四种写法的汇编代码竟然都一样:

  1. movl -0x8(%rbp), %ecx
  2. addl $0x1, %ecx
  3. movl %ecx, -0x8(%rbp)

从这一点看,似乎四种写法的开销都是两次内存访问。但是他们的功能不都一样,我们可以这样再改:

  1. int main() {
  2. int i = 0;
  3. int m;
  4. m = i++;
  5. m = ++i;
  6. m = i+=1;
  7. m = i=i+1;
  8. return 0;
  9. }

再看汇编,发现了变化:

  1. a.out`main:
  2. 0x100000f70 <+0>: pushq %rbp
  3. 0x100000f71 <+1>: movq %rsp, %rbp
  4. 0x100000f74 <+4>: xorl %eax, %eax
  5. 0x100000f76 <+6>: movl $0x0, -0x4(%rbp)
  6. 0x100000f7d <+13>: movl $0x0, -0x8(%rbp)
  7. 0x100000f84 <+20>: movl -0x8(%rbp), %ecx
  8. 0x100000f87 <+23>: movl %ecx, %edx
  9. 0x100000f89 <+25>: addl $0x1, %edx
  10. 0x100000f8c <+28>: movl %edx, -0x8(%rbp)
  11. 0x100000f8f <+31>: movl %ecx, -0xc(%rbp)
  12. 0x100000f92 <+34>: movl -0x8(%rbp), %ecx
  13. 0x100000f95 <+37>: addl $0x1, %ecx
  14. 0x100000f98 <+40>: movl %ecx, -0x8(%rbp)
  15. 0x100000f9b <+43>: movl %ecx, -0xc(%rbp)
  16. 0x100000f9e <+46>: movl -0x8(%rbp), %ecx
  17. 0x100000fa1 <+49>: addl $0x1, %ecx
  18. 0x100000fa4 <+52>: movl %ecx, -0x8(%rbp)
  19. 0x100000fa7 <+55>: movl %ecx, -0xc(%rbp)
  20. 0x100000faa <+58>: movl -0x8(%rbp), %ecx
  21. 0x100000fad <+61>: addl $0x1, %ecx
  22. 0x100000fb0 <+64>: movl %ecx, -0x8(%rbp)
  23. 0x100000fb3 <+67>: movl %ecx, -0xc(%rbp)
  24. 0x100000fb6 <+70>: popq %rbp
  25. 0x100000fb7 <+71>: retq

m = i++; 对应的汇编为:

  1. movl -0x8(%rbp), %ecx
  2. movl %ecx, %edx
  3. addl $0x1, %edx
  4. movl %edx, -0x8(%rbp)
  5. movl %ecx, -0xc(%rbp)

三次内存访问,用了两个寄存器。

另外三种写法的汇编为:

  1. movl -0x8(%rbp), %ecx
  2. addl $0x1, %ecx
  3. movl %ecx, -0x8(%rbp)
  4. movl %ecx, -0xc(%rbp)

同样三次内存访问,不过相比之下,只用了一个寄存器。

这么一看,由于寄存器操作速度是相当快的,访问内存才是效率的决定因素,所以四种写法效率差别并不大,甚至可以忽略不计。硬要说就是 i++; 这种写法最慢,另外三种写法一样。

结论

既生 ++i ,何生 i++ ?唯一一个理由就是,手指有些短,习惯先按 i 再按 + 。

不过用生命中宝贵的几秒钟来纠结 CPU 的几个时钟周期,真的不值得。窗外的月光,更令人着迷。

++i? i++? i+=1? i=i+1? 何必纠结?的更多相关文章

  1. 360浏览器下jquery.validate.unobtrusive的日期验证问题

    今天在招聘频道(job.cnblogs.com)遭遇这样一个问题——在360浏览器下,在一个表单验证中,虽然输入了有效的日期,却总是提示日期格式错误,见下图: 而在Chrome/Safari/Fire ...

  2. android基础篇学习心得

    android技术中,线程.进程.JNI.IPC和各个小框架结构是基本功.在跟随高焕堂老师的android程序猿到架构师之路系列视频中 学习完基础篇之后,颇有些心得,记录下来. android开发就是 ...

  3. EntityFramework Core查询问题集锦(一)

    前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的 ...

  4. 【EntityFramework 6.1.3】个人理解与问题记录(2)

    前言 才看完一季动漫,完结撒花,末将于禁,原为曹家世代赴汤蹈火!想必看过的都会知道这个,等一下要不吐槽一下翻拍的真人版,○( ^皿^)っHiahia-,好了快醒醒改办正事儿了,好的,我们接着上一篇文章 ...

  5. EntityFramework Core问题处理集锦(一)

    前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的 ...

  6. android 职业 转行

    不知道多少人和我一样.学安卓安卓工作.成了一件很烦躁的事情.甚至迷茫.    起初学安卓,是因为安卓流行,所以有兴趣,想要学.那个时候想做一个应用,想对安卓手机有个了解,比如获取手机短信.没有太在意工 ...

  7. SSE图像算法优化系列三:超高速导向滤波实现过程纪要(欢迎挑战)

    自从何凯明提出导向滤波后,因为其算法的简单性和有效性,该算法得到了广泛的应用,以至于新版的matlab都将其作为标准自带的函数之一了,利用他可以解决的所有的保边滤波器的能解决的问题,比如细节增强.HD ...

  8. React学习-React初识

    一.前言 为什么要去学习React呢,关于前端三大框架Angular,Vue,React其实都得去学吧,因为大家都在用啊,大家都再谈论啊,面试什么的都要求,没办法,曾几何时,大家都说求求大佬们别坑新了 ...

  9. 专访Nick McKeown:网络领域的游戏颠覆者

    如果要找到一个过去10年在网络领域最热的词汇,那么非SDN(软件定义网络)莫属.在过去的十年间无论是学术机构还是标准组织,无论是电信巨擘还是互联网大厂都成其拥趸. 然而几乎每一件SDN的重大事件都离不 ...

随机推荐

  1. ceph 存储池PG查看和PG存放OSD位置

    1. 查看PG (ceph-mon)[root@controller /]# ceph pg stat 512 pgs: 512 active+clean; 0 bytes data, 1936 MB ...

  2. H - Graphics(dfs)

    H - Graphics   Time Limit:1000MS     Memory Limit:131072KB     64bit IO Format:%lld & %llu Submi ...

  3. 洛谷P2664 树上游戏(点分治)

    传送门 题解 因为一个sb错误调了一个晚上……鬼晓得我为什么$solve(rt)$会写成$solve(v)$啊!!!一个$O(logn)$被我硬生生写成$O(n)$了竟然还能过$5$个点……话说还一直 ...

  4. 前端传给后端的数据类型为ImmutableMultiDict 咋办

    https://segmentfault.com/q/1010000002802028 偷得人家的答案     以下是解决办法:::: -------------------------------- ...

  5. PHP性能优化四(业务逻辑中使用场景)

    php脚本性能,很多时候依赖于你的php版本.你的web server环境和你的代码的复杂度. Hoare曾经说过“过早优化是一切不幸的根源”.当你想要让你的网站更快运转的时候,你才应该去做优化的事情 ...

  6. 【JVM】jvm启动参数

    -server -Xmx2048m -Xms1500m -Xmn1024m -XX:PermSize=128m -Xss256k -XX:+DisableExplicitGC -XX:+UseConc ...

  7. [转] 红帽7搭建Zabbix监控

    zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案. zabbix能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制以让系统管理员快速定位/解决 ...

  8. IDEA 引入外部jar包 pom 配置,防止打包失败

    1. <!--添加外部依赖-->        <dependency>            <groupId>Ice</groupId>       ...

  9. 1.HTML练习(二)

    一.表格练习: 1.<table>标签:声明一个表格,它的常用属性如下: border属性             定义表格的边框,设置值是数值 cellpadding属性     定义单 ...

  10. python------对于面向对象的理解

    python中一切皆为对象 其实面向对象没什么高大上的东西,只不过把我们平时对于事物的描述和动作系统的总结成了一个定义事物的方法而已. 我们平时向别人介绍一个他(她)从未见过的东西,会从外形和外貌特征 ...