从汇编代码的角度观察switch与if...else,乘除与移位的差别
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,乘除与移位的差别相关推荐
- gdb 查看,执行汇编代码
用gdb 查看汇编代码, 采用disassemble 和 x 命令. nexti, stepi 可以单步指令执行 如下例: -------------------------------------- ...
- GCC如何编译内嵌汇编代码
内核代码绝大部分使用C 语言编写,只有一小部分使用汇编语言编写,例如与特定体系结构相关的代码和对性能影响很大的代码.GCC提供了内嵌汇编的功能,可以在C代码中直接内嵌汇编语言语句,大大方便了程序设计 ...
- 编译hotspot_从Hotspot JIT编译器打印生成的汇编代码
编译hotspot 有时,在对Java应用程序进行性能分析时,有必要了解Hotspot JIT编译器生成的汇编代码. 这对于确定已做出的优化决策以及我们的代码更改如何影响生成的汇编代码非常有用. 在调 ...
- 从Hotspot JIT编译器打印生成的汇编代码
有时,在对Java应用程序进行性能分析时,有必要了解Hotspot JIT编译器生成的汇编代码. 这对于确定已做出的优化决策以及我们的代码更改如何影响生成的汇编代码非常有用. 在调试并行算法以确保已按 ...
- 【OS学习笔记】二十八 保护模式八:任务切换对应的汇编代码之内核代码
本汇编代码对应以下两篇文章对应的内核汇编代码: OS学习笔记]二十六 保护模式八:任务门-任务切换 [OS学习笔记]二十七 保护模式八:任务切换的方法之----jmp与call的区别以及任务的中断嵌套 ...
- c理c利用计算机怎么弹,通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的...
通过汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的 计算机的工作方式: 现代计算机的基本体系结构都是采用冯诺依曼结构,冯诺依曼的设计思想最重要之处是"存储程序"的这个概念 ...
- endp 汇编start_常见汇编代码
# 常见汇编代码 # 1. 编写程序:比较AX,BX,CX中带符号数的大小,将最大的数放在AX中 code segment assume cs:code mov ax,32 mov bx,74 mov ...
- keil4如何将c语言转换成汇编语言_Keil 中关于C语言编译生成汇编代码函数名规则...
在keil 中 C语言的函数有带参数和不带参数之分. 一般的资料里说fun(void)类型的函数不带参数,所以,keil编译器生成的汇编的调用地址(函数名) 为fun.这没有错.事实上,不管C语言的函 ...
- C++实现的利用LR(1)分析表对赋值表达式进行语法制导翻译生成四元式及汇编代码
赋值语句的语法制导翻译 后续已完善算术运算文法.赋值文法.布尔运算文法.if.while.do-while和复合语句文法,编译器项目已上传GitHub,https://github.com/sleep ...
最新文章
- mysql引号问题_MySQL中引号的问题
- excel相乘再相加_excel将两组数据相乘后再求和该怎么操作?
- Java NIO类库Selector机制解析--转
- pstools套件在渗透中的应用详解
- 2020亚太杯数学建模_比赛 | 2020年APMCM亚太地区大学生数学建模竞赛
- 机器学习相关知识 大佬博客整理
- linux 搜索命令 历史,Linux 控制台神器:搜索历史命令 Ctrl + R ( ctrl + r to search the history command )...
- 1109: 数根(函数专题)
- eclipse maven项目 class类部署不到tomcat下_Springboot介绍以及用Eclipse搭建一个简单的Springboot项目教程
- STM32之端口复用与重映射
- redistemplate hash 过期时间_Redis过期监听——订单超时-取消
- 销傲销售过程GSP管理系统功能概述
- html向上移动图片代码,图片随网页上下移动的代码实例
- linux usb有线网卡驱动_Linux下安装USB网卡驱动 | 学步园
- 三数之和java实现ArrayList-leetcode算法编程-探索字节跳动面试
- Dilated Convolution —— 空洞卷积(膨胀卷积)
- 使用朋也社区搭建自己的社区网站就是这么简单~~
- Windows10 64位 + caffe + Matlab -- cpu版本
- DR5加强版2019全新 | PS磨皮插件高端人像后期修图工具
- bootstrap editable 动态改变列的编辑状态
热门文章
- 微信小程序相关项目实例集合
- CSS3边框图片、边框阴影、文本阴影
- ES6_proxy_note
- python文件のpandas操作
- ireport怎么套打_方向盘套你选对了吗?老司机告诉你该这样做|酷斯特玩车
- cleanmymac 4.2_市委刚刚批准:11月1号立即执行! 农业银行存款利率4.2%,1万元存1年,有多少利息?...
- Android生命周期工具类,Android倒计时工具类
- prev php,PHP prev() 函数 ——jQuery中文网
- 解决“chrome正受到自动测试软件的控制”信息栏显示问题(转)
- C语言第一个字节地址,C语言字节对齐详解