本文主要讲解Switch的汇编代码
![fdf696ff3d4931225c290f86c439d7da.png]()
Switch
1、假设switch语句的分支比较少时(例如3,少于4的时候没有意义),没有必要使用次结构,相当于if-else
2、各个分支常量的差值较大时,编译器会在效率还是内存进行取舍,这时编译器还是会编译成类似于if-else的结构
3、在分支比较多的时候,在编译的时候会生成一个表,不同的case通过跳转表的不同地址,每个地址占四个字节。
案例分析
首先作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130595548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
1、当case有3个时
void func(int a){ switch (a) { case 1: printf("打坐"); break; case 2: printf("加红"); break; case 3: printf("加蓝"); break; default: printf("啥也不干"); break;
}
}int main(int argc, char * argv[]) {
func(1);
断点调试func
![e26523fa418a502ff4f405c20ef44c68.webp]()
其中3个case的汇编是以if-else的形式
2、如果是4个case呢?
void func(int a){ switch (a) { case 1: printf("打坐"); break; case 2: printf("加红"); break; case 3: printf("加蓝"); break; case 4: printf("打怪"); break; default: printf("啥也不干"); break;
}
}int main(int argc, char * argv[]) {
func(1);
}
此时w8就是传入的参数,即w8=4
![4e5424f9053d2e7c285b68504bf28378.webp]()
subs w8, w8, #0x1 :subs会影响目标寄存器,即影响w8(注:但不执行汇编命令:ni)
![c519db491aea8d290530c8b650e26b02.webp]()
ubfx x9, x9, #0, #32:将x9高32位清零,下面通过修改x9寄存器的值来验证:确实是将高32位清零(从x9第0位开始,保留低32位,高位用0补齐)
![59fa0a0440b731274d28a2ab1a063a06.webp]()
下面是实际操作后的结果
![692aee860b1072b479ce98b9010c142c.webp]()
执行cmp x9 #0x4 ~ ldr x11,[sp],在这里是x9和4比较,判断是否等于。此时的结果是不等于则继续往下执行
![7931dbfb3c5c9941d314041e0439f832.webp]()
ldrsw x10, [x8, x11, lsl #2](先运算后面,再运算前面):将x8的值加上x11(需要先将x11左移2位,即0x3左移2位,为1100,为12)作为地址,然后将地址的值给x10,此时x10的值就是-24 ????(即0xffffffffffffffe8)
![f8416f73461085eff2596d867520716f.webp]()
执行ldrsw x10, [x8, x11, lsl #2]后的x10
![c2f852521c44abcd9bbb1444f5800248.webp]()
从x8 = 0x0000000102e22828 与最后0x102e22824对比,可以看出,x8的地址是func执行完后下一句代码的地址
![ddca54aedd416105f01c016f84a20f1c.webp]()
add x9, x8, x10:x8的地址加上x10偏移(由于x10为负数,所以是x8-x10),给到x9.即x9的值为 0x102e22828-0x18(24的十六进制)=0x102e22810,即到下面这句,这里就是走到default
![36536df782381fa16101c79fea9c65f5.webp]()
执行到ldp x29, x30, [sp, #0x10],输出啥也不干(lldb)
![5335ec0437237830e393ef8613dea91b.webp]()
3、修改案例
void func(int a){ switch (a) { case 5: printf("打坐"); break; case 6: printf("加红"); break; case 7: printf("加蓝"); break; case 8: printf("打怪"); break; default: printf("啥也不干"); break;
}
}int main(int argc, char * argv[]) {
func(4);
}
其汇编如下,发现subs减的是5
![76a0deb15fbfd40b62f08eb27b0674d1.webp]()
汇编代码分析如下
![225ad3ba845a3c3cc6b7141ff52fe1fe.webp]()
4、修改2:将第二个case修改为2
void func(int a){ switch (a) { case 5: printf("打坐"); break; case 2: printf("加红"); break; case 7: printf("加蓝"); break; case 8: printf("打怪"); break; default: printf("啥也不干"); break;
}
}
汇编如下,从这里可以发现,subs后减的是最小的case
![00ad6ee6a5438ffd87e2590e410071eb.webp]()
疑问1:其中的cmp x9,#0x6,这里的0x6又是怎么来的呢?其实是最小case与最大case(即8-2)的差值,
疑问2:为什么要这么比较呢?主要是为了确认传入的参数是否在[最小case,最大case]这个区间内,如果不在,则走default
疑问3:为什么是无符号?因为如果传入比最小case小的值,例如传入的是1,得到的是一个负数,
分析
switch总结
如果case很大呢?
- 只要是连续的就行,因为汇编会减去最小case
- 如果case之间相差太大了,编译器会进行优化,变成if-else
void func(int a){ switch (a) { case 1: printf("打坐"); break; case 400: printf("加红"); break; case 800: printf("加蓝"); break; case 8: printf("打怪"); break; default: printf("啥也不干"); break;
}
}int main(int argc, char * argv[]) {
func(4);
}
![7a6a1e9c602395e3dbd84665c7494f28.webp]()
首先作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS开发交流群:130595548,不管你是小白还是大牛都欢迎入驻 ,让我们一起进步,共同发展!(群内会免费提供一些群主收藏的免费学习书籍资料以及整理好的几百道面试题和答案文档!)
总结
1、假设switch语句的分支比较少时(例如3,少于4的时候没有意义),没有必要使用次结构,相当于if-else
2、各个分支常量的差值较大时,编译器会在效率还是内存进行取舍,这时编译器还是会编译成类似于if-else的结构
3、在分支比较多的时候,在编译的时候会生成一个表(跳转表每个地址四个字节)。