我们经常会在代码里面用到条件判断语句,这类语句通常为有if else 和 switch。但是这两者有什么区别呢?其实我一直原来都不知道,也不了解,反正用的时候如何复杂的判断,比如说字符串是否相同啊,在OC里面都是用if else;那如果是一般比较简单的标志物判断呢? 看心情,想敲if else就用if else,想敲switch就用switch ,一直没什么概念,为什么要写if else或者是switch? 或者是脑海里曾经想到大学老师说的switch效率更高,但是为什么switch效率会更高?忘了!这应该是大多数程序员的现状吧。现在机缘巧合之下,重新接触汇编,那我们就从汇编的角度看看它们两者之间有什么区别吧
1.if else的汇编是什么样的
本人是个iOS开发人员,所以选用OC的工程里 在main函数里加了一个函数fun1来实现if else:
void fun1(int a )
{
if (a == 1) {
printf("hello1");
}
else if (a == 2) {
printf("hello3");
}
else
{
printf("hello");
}
}
int main(int argc, char * argv[]) {
fun1(2);
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
真机编译后我得到fun1的汇编代码为:
ifelse`fun1:
0x100af6098 <+0>: sub sp, sp, #0x20 ; =0x20
0x100af609c <+4>: stp x29, x30, [sp, #0x10]
0x100af60a0 <+8>: add x29, sp, #0x10 ; =0x10
0x100af60a4 <+12>: stur w0, [x29, #-0x4]
-> 0x100af60a8 <+16>: ldur w8, [x29, #-0x4]
0x100af60ac <+20>: cmp w8, #0x1 ; =0x1
0x100af60b0 <+24>: b.ne 0x100af60c4 ; <+44> at main.m:16:14
0x100af60b4 <+28>: adrp x0, 1
0x100af60b8 <+32>: add x0, x0, #0xf75 ; =0xf75
0x100af60bc <+36>: bl 0x100af6588 ; symbol stub for: printf
0x100af60c0 <+40>: b 0x100af60ec ; <+84> at main.m:23:1
0x100af60c4 <+44>: ldur w8, [x29, #-0x4]
0x100af60c8 <+48>: cmp w8, #0x2 ; =0x2
0x100af60cc <+52>: b.ne 0x100af60e0 ; <+72> at main.m
0x100af60d0 <+56>: adrp x0, 1
0x100af60d4 <+60>: add x0, x0, #0xf7c ; =0xf7c
0x100af60d8 <+64>: bl 0x100af6588 ; symbol stub for: printf
0x100af60dc <+68>: b 0x100af60ec ; <+84> at main.m:23:1
0x100af60e0 <+72>: adrp x0, 1
0x100af60e4 <+76>: add x0, x0, #0xf83 ; =0xf83
0x100af60e8 <+80>: bl 0x100af6588 ; symbol stub for: printf
0x100af60ec <+84>: ldp x29, x30, [sp, #0x10]
0x100af60f0 <+88>: add sp, sp, #0x20 ; =0x20
0x100af60f4 <+92>: ret
从代码里 我们可以清晰的看到 一共做了两次比较,分别是cmp w8, #0x1 和 cmp w8, #0x2 ;(CMP为汇编中的比较指令,实际上做了减法,两数相减是否大于0来判断两数的大小或者相等)
比如说第一句cmp w8, #0x1 用W8的寄存器里的值跟#0x1(即1)比较 ,w8存放是 fun1(2)中的参数2;可见,用if else 写,在汇编里我们比较了两次。
2.switch的汇编是什么样的
那我们再看看switch 是什么样子的?
同样的地方添加代码
void fun2(int a )
{
switch (a) {
case 1:
printf("hello1");
break;
case 2:
printf("hello2");
break;
default:
printf("hello");
break;
}
}
int main(int argc, char * argv[]) {
fun2(2);
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
编译会我们得到的fun2汇编代码为:
ifelse`fun2:
0x102eda0f8 <+0>: sub sp, sp, #0x20 ; =0x20
0x102eda0fc <+4>: stp x29, x30, [sp, #0x10]
0x102eda100 <+8>: add x29, sp, #0x10 ; =0x10
0x102eda104 <+12>: stur w0, [x29, #-0x4]
-> 0x102eda108 <+16>: ldur w8, [x29, #-0x4]
0x102eda10c <+20>: cmp w8, #0x1 ; =0x1
0x102eda110 <+24>: str w8, [sp, #0x8]
0x102eda114 <+28>: b.eq 0x102eda12c ; <+52> at main.m
0x102eda118 <+32>: b 0x102eda11c ; <+36> at main.m
0x102eda11c <+36>: ldr w8, [sp, #0x8]
0x102eda120 <+40>: cmp w8, #0x2 ; =0x2
0x102eda124 <+44>: b.eq 0x102eda13c ; <+68> at main.m
0x102eda128 <+48>: b 0x102eda14c ; <+84> at main.m
0x102eda12c <+52>: adrp x0, 1
0x102eda130 <+56>: add x0, x0, #0xf75 ; =0xf75
0x102eda134 <+60>: bl 0x102eda588 ; symbol stub for: printf
0x102eda138 <+64>: b 0x102eda158 ; <+96> at main.m:42:1
0x102eda13c <+68>: adrp x0, 1
0x102eda140 <+72>: add x0, x0, #0xf89 ; =0xf89
0x102eda144 <+76>: bl 0x102eda588 ; symbol stub for: printf
0x102eda148 <+80>: b 0x102eda158 ; <+96> at main.m:42:1
0x102eda14c <+84>: adrp x0, 1
0x102eda150 <+88>: add x0, x0, #0xf83 ; =0xf83
0x102eda154 <+92>: bl 0x102eda588 ; symbol stub for: printf
0x102eda158 <+96>: ldp x29, x30, [sp, #0x10]
0x102eda15c <+100>: add sp, sp, #0x20 ; =0x20
0x102eda160 <+104>: ret
震惊!!!我们得到汇编代码是一模一样的! 也就是说 :我们的if else 和 switch在底层是一样的!终于得到答案了!看到这里我们可以退出去了,以后谁跟我说switch效率高,我啪啪啪打脸你!
3.增加if else的判断会如何?
但是,我们再细想下,这会不会是个巧合呢?那我们在看看加多几个条件后是否还是一样?
我们把fun1添加到5个判断逻辑:
void fun1(int a )
{
if (a == 1) {
printf("hello1");
}
else if (a == 2) {
printf("hello2");
}
else if (a == 3) {
printf("hello3");
}
else if (a == 4) {
printf("hello4");
}
else if (a == 5) {
printf("hello5");
}
else
{
printf("hello");
}
}
编译后得到的汇编代码为:
ifelse`fun1:
0x102b62038 <+0>: sub sp, sp, #0x20 ; =0x20
0x102b6203c <+4>: stp x29, x30, [sp, #0x10]
0x102b62040 <+8>: add x29, sp, #0x10 ; =0x10
0x102b62044 <+12>: stur w0, [x29, #-0x4]
-> 0x102b62048 <+16>: ldur w8, [x29, #-0x4]
0x102b6204c <+20>: cmp w8, #0x1 ; =0x1
0x102b62050 <+24>: b.ne 0x102b62064 ; <+44> at main.m:16:14
0x102b62054 <+28>: adrp x0, 1
0x102b62058 <+32>: add x0, x0, #0xf69 ; =0xf69
0x102b6205c <+36>: bl 0x102b6257c ; symbol stub for: printf
0x102b62060 <+40>: b 0x102b620e0 ; <+168> at main.m:32:1
0x102b62064 <+44>: ldur w8, [x29, #-0x4]
0x102b62068 <+48>: cmp w8, #0x2 ; =0x2
0x102b6206c <+52>: b.ne 0x102b62080 ; <+72> at main.m:19:14
0x102b62070 <+56>: adrp x0, 1
0x102b62074 <+60>: add x0, x0, #0xf70 ; =0xf70
0x102b62078 <+64>: bl 0x102b6257c ; symbol stub for: printf
0x102b6207c <+68>: b 0x102b620e0 ; <+168> at main.m:32:1
0x102b62080 <+72>: ldur w8, [x29, #-0x4]
0x102b62084 <+76>: cmp w8, #0x3 ; =0x3
0x102b62088 <+80>: b.ne 0x102b6209c ; <+100> at main.m:22:14
0x102b6208c <+84>: adrp x0, 1
0x102b62090 <+88>: add x0, x0, #0xf77 ; =0xf77
0x102b62094 <+92>: bl 0x102b6257c ; symbol stub for: printf
0x102b62098 <+96>: b 0x102b620e0 ; <+168> at main.m:32:1
0x102b6209c <+100>: ldur w8, [x29, #-0x4]
0x102b620a0 <+104>: cmp w8, #0x4 ; =0x4
0x102b620a4 <+108>: b.ne 0x102b620b8 ; <+128> at main.m:25:14
0x102b620a8 <+112>: adrp x0, 1
0x102b620ac <+116>: add x0, x0, #0xf7e ; =0xf7e
0x102b620b0 <+120>: bl 0x102b6257c ; symbol stub for: printf
0x102b620b4 <+124>: b 0x102b620e0 ; <+168> at main.m:32:1
0x102b620b8 <+128>: ldur w8, [x29, #-0x4]
0x102b620bc <+132>: cmp w8, #0x5 ; =0x5
0x102b620c0 <+136>: b.ne 0x102b620d4 ; <+156> at main.m
0x102b620c4 <+140>: adrp x0, 1
0x102b620c8 <+144>: add x0, x0, #0xf85 ; =0xf85
0x102b620cc <+148>: bl 0x102b6257c ; symbol stub for: printf
0x102b620d0 <+152>: b 0x102b620e0 ; <+168> at main.m:32:1
0x102b620d4 <+156>: adrp x0, 1
0x102b620d8 <+160>: add x0, x0, #0xf8c ; =0xf8c
0x102b620dc <+164>: bl 0x102b6257c ; symbol stub for: printf
0x102b620e0 <+168>: ldp x29, x30, [sp, #0x10]
0x102b620e4 <+172>: add sp, sp, #0x20 ; =0x20
0x102b620e8 <+176>: ret
代码里一共有5个cmp,刚好跟我if else 逻辑是对应;然后我不断把if else的逻辑增加,发现cmp数量对应增加。所以可以验证:if else编译后再汇编代码里有多少个if分支就会对比多少次。
4.增加switch的判断会如何?
然后我们再来看看switch的是否一样?
当我把switch分支写到三个时,还是和if一样,但是来到4个的时候
void fun2(int a )
{
switch (a) {
case 1:
printf("hello1");
break;
case 2:
printf("hello2");
break;
case 3:
printf("hello3");
break;
case 4:
printf("hello3");
break;
default:
printf("hello");
break;
}
}
我们得到的汇编代码就不一样了
ifelse`fun2:
0x100e860ac <+0>: sub sp, sp, #0x20 ; =0x20
0x100e860b0 <+4>: stp x29, x30, [sp, #0x10]
0x100e860b4 <+8>: add x29, sp, #0x10 ; =0x10
0x100e860b8 <+12>: stur w0, [x29, #-0x4]
-> 0x100e860bc <+16>: ldur w8, [x29, #-0x4]
0x100e860c0 <+20>: subs w8, w8, #0x1 ; =0x1
0x100e860c4 <+24>: mov x9, x8
0x100e860c8 <+28>: ubfx x9, x9, #0, #32
0x100e860cc <+32>: cmp x9, #0x3 ; =0x3
0x100e860d0 <+36>: str x9, [sp]
0x100e860d4 <+40>: b.hi 0x100e86130 ; <+132> at main.m
0x100e860d8 <+44>: adrp x8, 0
0x100e860dc <+48>: add x8, x8, #0x148 ; =0x148
0x100e860e0 <+52>: ldr x11, [sp]
0x100e860e4 <+56>: ldrsw x10, [x8, x11, lsl #2]
0x100e860e8 <+60>: add x9, x8, x10
0x100e860ec <+64>: br x9
0x100e860f0 <+68>: adrp x0, 1
0x100e860f4 <+72>: add x0, x0, #0xf69 ; =0xf69
0x100e860f8 <+76>: bl 0x100e8657c ; symbol stub for: printf
0x100e860fc <+80>: b 0x100e8613c ; <+144> at main.m:68:1
0x100e86100 <+84>: adrp x0, 1
0x100e86104 <+88>: add x0, x0, #0xf70 ; =0xf70
0x100e86108 <+92>: bl 0x100e8657c ; symbol stub for: printf
0x100e8610c <+96>: b 0x100e8613c ; <+144> at main.m:68:1
0x100e86110 <+100>: adrp x0, 1
0x100e86114 <+104>: add x0, x0, #0xf77 ; =0xf77
0x100e86118 <+108>: bl 0x100e8657c ; symbol stub for: printf
0x100e8611c <+112>: b 0x100e8613c ; <+144> at main.m:68:1
0x100e86120 <+116>: adrp x0, 1
0x100e86124 <+120>: add x0, x0, #0xf77 ; =0xf77
0x100e86128 <+124>: bl 0x100e8657c ; symbol stub for: printf
0x100e8612c <+128>: b 0x100e8613c ; <+144> at main.m:68:1
0x100e86130 <+132>: adrp x0, 1
0x100e86134 <+136>: add x0, x0, #0xf8c ; =0xf8c
0x100e86138 <+140>: bl 0x100e8657c ; symbol stub for: printf
0x100e8613c <+144>: ldp x29, x30, [sp, #0x10]
0x100e86140 <+148>: add sp, sp, #0x20 ; =0x20
0x100e86144 <+152>: ret
里面只有一个cmp,意思就是,只比较一次,那他是怎么实现的呢?
通过上面的汇编代码 其中 cmp x9,#0x3 中的#0x3(即值为3)是我们分支代码的最大值(4) 减去最小值(1) 得到的3.
然后我们再看下 参数W0 (我们传入的2)存到W8里面 然后走
0x100e860c0 <+20>: subs w8, w8, #0x1 ; =0x1 //意思是把刚才传入W8的2减去1 再保存到w8里面; 0x1是我们case的最小值
0x100e860c4 <+24>: mov x9, x8 //x9 存放x8
0x100e860c8 <+28>: ubfx x9, x9, #0, #32 //把高32清零 保证里面的值为前面w8的值
0x100e860cc <+32>: cmp x9, #0x3 //意思是 入参的2 减去最小case值1 在跟case的最大和最小值得差值 3比较大小
b.hi 为无符号比较!
上面说的是什么鬼呢? 换人话说就是:假设 X为传入的值 1 为最小case. 4为最大case
那逻辑就应该是这样的
(x - 1)得到一个值
4 - 1 最大case - 最小case 得到一个值
(x - 1)与 (4 - 1) 进行无符号比较:
a.当x为 1,2,3,4 减1后跟(4-1)比较 是小于或者等于 的关系
b.当x 大于4 是大于(4-1)的关系
c.当0和负数的情况 减去1后再去符号 那么我们得到的是一个大数 ;因为有符号的 最高为为1,无符号是,那肯定是个大数,会大于(4-1)的关系
意思很明白了,当得到的值小于等于(4-1)时,走分支内容,大于时走default。
原来switch确实在底层做了一个优化,在程序结束的后面0x100e86144 <+152>: ret 的后面 148后面写了一张表,一共 最大case - 最小case +default个值;在比较的时候只需要运算后去这个表里面的值即可完成,而不是一个个去比较了。
这个表的结构具体要看我们case是怎么样写
比如说是连续的1,2,3,4,default 那表结构应该是 case1case2case3case4default;
如果数不连续的 1 3 4 7 default 那表结构应该是 case1defaultcase3case4defaultdefaultcase7default;
上面就算的差值(x - 1)与 (4 - 1) 刚好是落在那个位置,直接取值了。
5.是不是所有的switch多case结构都是这样呢?
我们看到当case不是连续的时候,他中间会插入default;那如果case差值很多,那岂不是要插入很多default,太浪费空间了吧?
我们继续看看 ,我们差别放到1000看看是什么样子的
void fun2(int a )
{
switch (a) {
case 1:
printf("hello1");
break;
case 2:
printf("hello2");
break;
case 3:
printf("hello3");
break;
case 4:
printf("hello3");
break;
case 1000:
printf("hello1000");
break;
default:
printf("hello");
break;
}
}
编译后得到的汇编代码为:
ifelse`fun2:
0x102dea080 <+0>: sub sp, sp, #0x20 ; =0x20
0x102dea084 <+4>: stp x29, x30, [sp, #0x10]
0x102dea088 <+8>: add x29, sp, #0x10 ; =0x10
0x102dea08c <+12>: stur w0, [x29, #-0x4]
-> 0x102dea090 <+16>: ldur w8, [x29, #-0x4]
0x102dea094 <+20>: cmp w8, #0x1 ; =0x1
0x102dea098 <+24>: str w8, [sp, #0x8]
0x102dea09c <+28>: b.eq 0x102dea0e4 ; <+100> at main.m
0x102dea0a0 <+32>: b 0x102dea0a4 ; <+36> at main.m
0x102dea0a4 <+36>: ldr w8, [sp, #0x8]
0x102dea0a8 <+40>: cmp w8, #0x2 ; =0x2
0x102dea0ac <+44>: b.eq 0x102dea0f4 ; <+116> at main.m
0x102dea0b0 <+48>: b 0x102dea0b4 ; <+52> at main.m
0x102dea0b4 <+52>: ldr w8, [sp, #0x8]
0x102dea0b8 <+56>: cmp w8, #0x3 ; =0x3
0x102dea0bc <+60>: b.eq 0x102dea104 ; <+132> at main.m
0x102dea0c0 <+64>: b 0x102dea0c4 ; <+68> at main.m
0x102dea0c4 <+68>: ldr w8, [sp, #0x8]
0x102dea0c8 <+72>: cmp w8, #0x4 ; =0x4
0x102dea0cc <+76>: b.eq 0x102dea114 ; <+148> at main.m
0x102dea0d0 <+80>: b 0x102dea0d4 ; <+84> at main.m
0x102dea0d4 <+84>: ldr w8, [sp, #0x8]
0x102dea0d8 <+88>: cmp w8, #0x3e8 ; =0x3e8
0x102dea0dc <+92>: b.eq 0x102dea124 ; <+164> at main.m
0x102dea0e0 <+96>: b 0x102dea134 ; <+180> at main.m
0x102dea0e4 <+100>: adrp x0, 1
0x102dea0e8 <+104>: add x0, x0, #0xf5d ; =0xf5d
0x102dea0ec <+108>: bl 0x102dea570 ; symbol stub for: printf
0x102dea0f0 <+112>: b 0x102dea140 ; <+192> at main.m:71:1
0x102dea0f4 <+116>: adrp x0, 1
0x102dea0f8 <+120>: add x0, x0, #0xf64 ; =0xf64
0x102dea0fc <+124>: bl 0x102dea570 ; symbol stub for: printf
0x102dea100 <+128>: b 0x102dea140 ; <+192> at main.m:71:1
0x102dea104 <+132>: adrp x0, 1
0x102dea108 <+136>: add x0, x0, #0xf6b ; =0xf6b
0x102dea10c <+140>: bl 0x102dea570 ; symbol stub for: printf
0x102dea110 <+144>: b 0x102dea140 ; <+192> at main.m:71:1
0x102dea114 <+148>: adrp x0, 1
0x102dea118 <+152>: add x0, x0, #0xf6b ; =0xf6b
0x102dea11c <+156>: bl 0x102dea570 ; symbol stub for: printf
0x102dea120 <+160>: b 0x102dea140 ; <+192> at main.m:71:1
0x102dea124 <+164>: adrp x0, 1
0x102dea128 <+168>: add x0, x0, #0xf86 ; =0xf86
0x102dea12c <+172>: bl 0x102dea570 ; symbol stub for: printf
0x102dea130 <+176>: b 0x102dea140 ; <+192> at main.m:71:1
0x102dea134 <+180>: adrp x0, 1
0x102dea138 <+184>: add x0, x0, #0xf80 ; =0xf80
0x102dea13c <+188>: bl 0x102dea570 ; symbol stub for: printf
0x102dea140 <+192>: ldp x29, x30, [sp, #0x10]
0x102dea144 <+196>: add sp, sp, #0x20 ; =0x20
0x102dea148 <+200>: ret
从代码中可以看出,又跟if else一样了 有5个cmp;可见,我们的编译器没那么笨!
6.case之间的差值是多少会是表和全部是cmp的临界值呢?
经过调试 在连续的case 只有一个case有差的时候 我得出的结果是45,当大于这个值,代码就跟if一样了;你们也可以验证下是否一致。
如果case都是不连续的时候,我得到的差别并非一致的,这里还可以继续探讨下,有知道的也请留言告诉我,万分感谢
结论:
1.当判断条件三个以及三个以内,if else 和 switch 的的汇编是没有区别的
2.当判断条件大于三个,而且条件之间的数值差较小时,switch会条件先计算出来放到函数后面,我们根据传入的参数计算出值,直接可以取值,效率更高
3.当判断条件大于三个,而且条件之间的数值差较大时,if else 和 switch 的的汇编是没有区别的
所以,当我们再次写if else 和 switch选择哪个时,就有了标准。
其实在硬件设备不断的提高,这个点差异用户根本无法感受出来,我觉得我们要学习是这种编程思想,在保证可读性的情况下,尽量提高程序运行消息。
谢谢观看!以上个人观点,由于个人技术欠佳,有谬处敬请指出!谢谢!