if else 和 switch 的区别

 我们经常会在代码里面用到条件判断语句,这类语句通常为有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选择哪个时,就有了标准。

其实在硬件设备不断的提高,这个点差异用户根本无法感受出来,我觉得我们要学习是这种编程思想,在保证可读性的情况下,尽量提高程序运行消息。

谢谢观看!以上个人观点,由于个人技术欠佳,有谬处敬请指出!谢谢!

 

 

 

 

 

上一篇:python 从深度相机realsense生成pcl点云


下一篇:机器学习的数学基础-(一、高等数学)