Switch与if…else

有以下两个函数,y值根据x值进行加法运算,功能相同,SumSwitch()是switch版本,SumIfElse()是if else版本:

 public int SumSwitch(int x,int y){switch(x){case 1:y+=1;break;case 2:y+=2;break;case 3:y+=3;break;case 4:y+=4;break;case 5:y+=5;break;case 6:y+=6;break;case 7:y+=7;break;case 8:y+=8;break;case 9:y+=9;break;default:y=0;break;}return y;}
 public int SumIfElse(int x,int y){if(x==1){y+=3;}else if(x==2){y+=4;}else if(x==3){y+=5;}else if(x==4){y+=6;}else if(x==5){y+=7;}else if(x==6){y+=8;}else if(x==7){y+=9;}else if(x==8){y+=10;}else if(x==9){y+=11;}else{y=0;}return y;}
}

用Monodevelop的AOT编译器编译后的汇编代码如下:

SumSwitch:

_AlgorithmTest_SumSwitch_int_int:
00000b40    pushl   %ebp
00000b41    movl    %esp, %ebp
00000b43    pushl   %ebx
00000b44    pushl   %edi
00000b45    pushl   %esi
00000b46    subl    $0xc, %esp
00000b49    movl    0xc(%ebp), %esi
00000b4c    movl    0x10(%ebp), %edi
00000b4f    calll   0xb54
00000b54    popl    %ebx
00000b55    addl    $0x4ac, %ebx            ## imm = 0x4AC
00000b5b    decl    %esi
00000b5c    cmpl    $0x9, %esi       //比较x与9,如果x大于等于9(x在上一行有减1)
00000b5f    jae 0xb9d                //则跳转到b9d行,既是源码的default处。
00000b61    movl    %esi, %ecx
00000b63    shll    $0x2, %ecx       //计算case的偏移量
00000b66    movl    0x14(%ebx), %eax //跳转表取址
00000b6c    addl    %ecx, %eax       //通过case的偏移量访存取接下来代码的绝对地址
00000b6e    movl    (%eax), %eax     //跳转表取址
00000b70    jmpl    *%eax            //跳转表跳转
00000b72    incl    %edi             //case1:+1
00000b73    jmp 0xb9f                //跳到return处
00000b75    addl    $0x2, %edi       //case2:+2
00000b78    jmp 0xb9f                //跳到return处
00000b7a    addl    $0x3, %edi       //下同
00000b7d    jmp 0xb9f
00000b7f    addl    $0x4, %edi
00000b82    jmp 0xb9f
00000b84    addl    $0x5, %edi
00000b87    jmp 0xb9f
00000b89    addl    $0x6, %edi
00000b8c    jmp 0xb9f
00000b8e    addl    $0x7, %edi
00000b91    jmp 0xb9f
00000b93    addl    $0x8, %edi
00000b96    jmp 0xb9f
00000b98    addl    $0x9, %edi
00000b9b    jmp 0xb9f
00000b9d    xorl    %edi, %edi
00000b9f    movl    %edi, %eax      //跳转表出口
00000ba1    leal    -0xc(%ebp), %esp
00000ba4    leal    -0xc(%ebp), %esp
00000ba7    popl    %esi
00000ba8    popl    %edi
00000ba9    popl    %ebx
00000baa    leave
00000bab    retl
00000bac    nopl    (%eax)

SumIfElse:

_AlgorithmTest_SumIfElse_int_int:
00000bb0    pushl   %ebp
00000bb1    movl    %esp, %ebp
00000bb3    pushl   %ebx
00000bb4    pushl   %edi
00000bb5    pushl   %esi
00000bb6    subl    $0xc, %esp
00000bb9    movl    0xc(%ebp), %esi
00000bbc    movl    0x10(%ebp), %edi
00000bbf    calll   0xbc4
00000bc4    popl    %ebx
00000bc5    addl    $0x43c, %ebx            ## imm = 0x43C
00000bcb    cmpl    $0x1, %esi  //比较x与1
00000bce    jne 0xbd8           //如果不等于1,跳转到行bd8
00000bd0    addl    $0x3, %edi  //如果等于1,则加3
00000bd3    jmp 0xc2f           //赋值后跳转到函数c2f行,跳出if else区域。
00000bd8    cmpl    $0x2, %esi  //比较x与2
00000bdb    jne 0xbe5           //以下同上
00000bdd    addl    $0x4, %edi
00000be0    jmp 0xc2f
00000be5    cmpl    $0x3, %esi
00000be8    jne 0xbf2
00000bea    addl    $0x5, %edi
00000bed    jmp 0xc2f
00000bf2    cmpl    $0x4, %esi
00000bf5    jne 0xbfc
00000bf7    addl    $0x6, %edi
00000bfa    jmp 0xc2f
00000bfc    cmpl    $0x5, %esi
00000bff    jne 0xc06
00000c01    addl    $0x7, %edi
00000c04    jmp 0xc2f
00000c06    cmpl    $0x6, %esi
00000c09    jne 0xc10
00000c0b    addl    $0x8, %edi
00000c0e    jmp 0xc2f
00000c10    cmpl    $0x7, %esi
00000c13    jne 0xc1a
00000c15    addl    $0x9, %edi
00000c18    jmp 0xc2f
00000c1a    cmpl    $0x8, %esi
00000c1d    jne 0xc24
00000c1f    addl    $0xa, %edi
00000c22    jmp 0xc2f
00000c24    xorl    %eax, %eax
00000c26    addl    $0xb, %edi
00000c29    cmpl    $0x9, %esi  //x与9比较,
00000c2c    cmovnel %eax, %edi  //用条件传送决定加11或赋值0
00000c2f    movl    %edi, %eax
00000c31    leal    -0xc(%ebp), %esp
00000c34    leal    -0xc(%ebp), %esp
00000c37    popl    %esi
00000c38    popl    %edi
00000c39    popl    %ebx
00000c3a    leave
00000c3b    retl
00000c3c    nopl    (%eax)

SumIfElse的汇编代码与源码基本相同,既是将x依次与1-9比较,直到找出相等的值(见注释)。SumSwitch与SumIfElse的版本相比主要是优化在两个地方。
第一是汇编代码会先判断x是否属于default(既不在1-9的范围呢):

00000b5c cmpl    $0x9, %esi       //比较x与9,如果x大于等于9(x在上一行有减1)
00000b5f    jae 0xb9d                //则跳转到b9d行,既是源码的default处。

而SumIfElse要将x与1-9全比对完毕才会决定是否要选择else的case。由于参数x的可能范围很大,SumSwitch先判定可能性大的case是合理的。

第二是跳转表,根据x的值直接跳转到对应的case:

00000b70 jmpl    *%eax

在这行代码之前编译器通过计算将跳转表的基址与x*2的值相加并存到 %eax中,然后直接跳转到此地址。

乘除与移位

以下是一个将全局变量i乘以2并返回的函数,分别用乘法与左移实现:

 public int Sum1(){int i2=i*2;return i2;}public int Sum2(){int i22=i<<1;return i22;}
_AlgorithmTest_Sum1:
00000a80    pushl   %ebp
00000a81    movl    %esp, %ebp
00000a83    pushl   %ebx
00000a84    subl    $0x4, %esp
00000a87    calll   0xa8c
00000a8c    popl    %ebx
00000a8d    addl    $0x574, %ebx            ## imm = 0x574
00000a93    movl    0x8(%ebp), %eax
00000a96    movl    0x8(%eax), %eax
00000a99    shll    %eax                //编译器将乘法换成了计算左移
00000a9b    leal    -0x4(%ebp), %esp
00000a9e    leal    -0x4(%ebp), %esp
00000aa1    popl    %ebx
00000aa2    leave
00000aa3    retl
00000aa4    nopw    %cs:(%eax,%eax)_AlgorithmTest_Sum2:
00000ab0    pushl   %ebp
00000ab1    movl    %esp, %ebp
00000ab3    pushl   %ebx
00000ab4    subl    $0x4, %esp
00000ab7    calll   0xabc
00000abc    popl    %ebx
00000abd    addl    $0x544, %ebx            ## imm = 0x544
00000ac3    movl    0x8(%ebp), %eax
00000ac6    movl    0x8(%eax), %eax
00000ac9    shll    %eax                //计算左移
00000acb    leal    -0x4(%ebp), %esp
00000ace    leal    -0x4(%ebp), %esp
00000ad1    popl    %ebx
00000ad2    leave
00000ad3    retl
00000ad4    nopw    %cs:(%eax,%eax)

两段汇编代码的内容完全一样,目前的编译器基本上默认会将简单的乘法转为左移(注释处)。

以下是一个将全局变量i除以2并返回的函数,分别用除法与右移实现:

 public int Sum3(){int i3=i/2;return i3;}public int Sum4(){int i4=i>>1;return i4;}
_AlgorithmTest_Sum3:
00000ae0    pushl   %ebp
00000ae1    movl    %esp, %ebp
00000ae3    pushl   %ebx
00000ae4    subl    $0x4, %esp
00000ae7    calll   0xaec
00000aec    popl    %ebx
00000aed    addl    $0x514, %ebx            ## imm = 0x514
00000af3    movl    0x8(%ebp), %eax
00000af6    movl    0x8(%eax), %ecx
00000af9    movl    %ecx, %eax
00000afb    shrl    $0x1f, %eax     //逻辑右移31位,%eax变为全0或全1
00000afe    addl    %ecx, %eax      //相当于将%ecx的值移到%eax中
00000b00    sarl    %eax                //将除法变为计算右移
00000b02    leal    -0x4(%ebp), %esp
00000b05    leal    -0x4(%ebp), %esp
00000b08    popl    %ebx
00000b09    leave
00000b0a    retl
00000b0b    nopl    (%eax,%eax)_AlgorithmTest_Sum4:
00000b10    pushl   %ebp
00000b11    movl    %esp, %ebp
00000b13    pushl   %ebx
00000b14    subl    $0x4, %esp
00000b17    calll   0xb1c
00000b1c    popl    %ebx
00000b1d    addl    $0x4e4, %ebx            ## imm = 0x4E4
00000b23    movl    0x8(%ebp), %eax
00000b26    movl    0x8(%eax), %eax
00000b29    sarl    %eax                //计算右移
00000b2b    leal    -0x4(%ebp), %esp
00000b2e    leal    -0x4(%ebp), %esp
00000b31    popl    %ebx
00000b32    leave
00000b33    retl
00000b34    nopw    %cs:(%eax,%eax)

两个版本的汇编代码都使用了算术右移。

结论:在case很多的情况下,switch会先判断default,然后只用一次跳转即可完成整个判断过程,相对于if…else按固定顺序进行依次判断可省去很多指令。另外,编译器会将乘除自动优化为移位运算,所以在源码中选择乘除或位移的写法就没有区别了。


维护日志:
2020-2-3:review

从汇编代码的角度观察switch与if...else,乘除与移位的差别相关推荐

  1. gdb 查看,执行汇编代码

    用gdb 查看汇编代码, 采用disassemble 和 x 命令. nexti, stepi 可以单步指令执行 如下例: -------------------------------------- ...

  2. GCC如何编译内嵌汇编代码

    内核代码绝大部分使用C  语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码.GCC提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计 ...

  3. 编译hotspot_从Hotspot JIT编译器打印生成的汇编代码

    编译hotspot 有时,在对Java应用程序进行性能分析时,有必要了解Hotspot JIT编译器生成的汇编代码. 这对于确定已做出的优化决策以及我们的代码更改如何影响生成的汇编代码非常有用. 在调 ...

  4. 从Hotspot JIT编译器打印生成的汇编代码

    有时,在对Java应用程序进行性能分析时,有必要了解Hotspot JIT编译器生成的汇编代码. 这对于确定已做出的优化决策以及我们的代码更改如何影响生成的汇编代码非常有用. 在调试并行算法以确保已按 ...

  5. 【OS学习笔记】二十八 保护模式八:任务切换对应的汇编代码之内核代码

    本汇编代码对应以下两篇文章对应的内核汇编代码: OS学习笔记]二十六 保护模式八:任务门-任务切换 [OS学习笔记]二十七 保护模式八:任务切换的方法之----jmp与call的区别以及任务的中断嵌套 ...

  6. c理c利用计算机怎么弹,通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的...

    通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的 计算机的工作方式: 现代计算机的基本体系结构都是采用冯诺依曼结构,冯诺依曼的设计思想最重要之处是"存储程序"的这个概念 ...

  7. endp 汇编start_常见汇编代码

    # 常见汇编代码 # 1. 编写程序:比较AX,BX,CX中带符号数的大小,将最大的数放在AX中 code segment assume cs:code mov ax,32 mov bx,74 mov ...

  8. keil4如何将c语言转换成汇编语言_Keil 中关于C语言编译生成汇编代码函数名规则...

    在keil 中 C语言的函数有带参数和不带参数之分. 一般的资料里说fun(void)类型的函数不带参数,所以,keil编译器生成的汇编的调用地址(函数名) 为fun.这没有错.事实上,不管C语言的函 ...

  9. C++实现的利用LR(1)分析表对赋值表达式进行语法制导翻译生成四元式及汇编代码

    赋值语句的语法制导翻译 后续已完善算术运算文法.赋值文法.布尔运算文法.if.while.do-while和复合语句文法,编译器项目已上传GitHub,https://github.com/sleep ...

最新文章

  1. mysql引号问题_MySQL中引号的问题
  2. excel相乘再相加_excel将两组数据相乘后再求和该怎么操作?
  3. Java NIO类库Selector机制解析--转
  4. pstools套件在渗透中的应用详解
  5. 2020亚太杯数学建模_比赛 | 2020年APMCM亚太地区大学生数学建模竞赛
  6. 机器学习相关知识 大佬博客整理
  7. linux 搜索命令 历史,Linux 控制台神器:搜索历史命令 Ctrl + R ( ctrl + r to search the history command )...
  8. 1109: 数根(函数专题)
  9. eclipse maven项目 class类部署不到tomcat下_Springboot介绍以及用Eclipse搭建一个简单的Springboot项目教程
  10. STM32之端口复用与重映射
  11. redistemplate hash 过期时间_Redis过期监听——订单超时-取消
  12. 销傲销售过程GSP管理系统功能概述
  13. html向上移动图片代码,图片随网页上下移动的代码实例
  14. linux usb有线网卡驱动_Linux下安装USB网卡驱动 | 学步园
  15. 三数之和java实现ArrayList-leetcode算法编程-探索字节跳动面试
  16. Dilated Convolution —— 空洞卷积(膨胀卷积)
  17. 使用朋也社区搭建自己的社区网站就是这么简单~~
  18. Windows10 64位 + caffe + Matlab -- cpu版本
  19. DR5加强版2019全新 | PS磨皮插件高端人像后期修图工具
  20. bootstrap editable 动态改变列的编辑状态

热门文章

  1. 微信小程序相关项目实例集合
  2. CSS3边框图片、边框阴影、文本阴影
  3. ES6_proxy_note
  4. python文件のpandas操作
  5. ireport怎么套打_方向盘套你选对了吗?老司机告诉你该这样做|酷斯特玩车
  6. cleanmymac 4.2_市委刚刚批准:11月1号立即执行! 农业银行存款利率4.2%,1万元存1年,有多少利息?...
  7. Android生命周期工具类,Android倒计时工具类
  8. prev php,PHP prev() 函数 ——jQuery中文网
  9. 解决“chrome正受到自动测试软件的控制”信息栏显示问题(转)
  10. C语言第一个字节地址,C语言字节对齐详解