美文网首页
if和switch在汇编中的区别

if和switch在汇编中的区别

作者: 某某香肠 | 来源:发表于2020-02-02 19:53 被阅读0次

    目前探究OC上的if和switch,其它语言我猜也是差不多的。

    if的汇编

    首先写下简单的if代码:

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSNumber *num = @(2);
        if (num.integerValue == 1) {
            NSLog(@"num = 1");
        } else if(num.integerValue == 2) {
            NSLog(@"num = 2");
        }
    }
    

    这里先用NSNumber的原因是防止被编译器优化掉,当然也可以再build setting里面设置防止编译器优化。

    其汇编代码是这样的:

        0x102b167b8 <+136>: cmp    x0, #0x1                  ; =0x1 
        0x102b167bc <+140>: b.ne   0x102b167d0               ; <+160> at ViewController.m
        0x102b167c0 <+144>: adrp   x0, 2
        0x102b167c4 <+148>: add    x0, x0, #0x60             ; =0x60 
        0x102b167c8 <+152>: bl     0x102b16b2c               ; symbol stub for: NSLog
        0x102b167cc <+156>: b      0x102b16800               ; <+208> at ViewController.m:25:1
        0x102b167d0 <+160>: adrp   x8, 2
        0x102b167d4 <+164>: add    x8, x8, #0xcc0            ; =0xcc0 
        0x102b167d8 <+168>: ldr    x9, [sp, #0x8]
        0x102b167dc <+172>: ldr    x1, [x8]
        0x102b167e0 <+176>: mov    x0, x9
        0x102b167e4 <+180>: bl     0x102b16b68               ; symbol stub for: objc_msgSend
        0x102b167e8 <+184>: cmp    x0, #0x2                  ; =0x2 
        0x102b167ec <+188>: b.ne   0x102b167fc               ; <+204> at ViewController.m
        0x102b167f0 <+192>: adrp   x0, 2
        0x102b167f4 <+196>: add    x0, x0, #0x80             ; =0x80 
        0x102b167f8 <+200>: bl     0x102b16b2c               ; symbol stub for: NSLog
    

    仔细看其实很好理解,在0x102b167b8行里让x0(num.integerValue)跟1比较,通过结果的不同决定后面跳转到哪个地方执行。

    接下来再看下switch的表现情况:

    switch的汇编代码(只有两个case)

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSNumber *num = @(2);
        NSInteger i = num.integerValue;
        switch (i) {
            case 1:
                NSLog(@"num = 1");
                break;
                
            case 2:
                NSLog(@"num = 2");
                break;
        }
    }
    

    汇编代码如下:

    
        0x10010e7d0 <+140>: ldr    x8, [sp, #0x18]
        0x10010e7d4 <+144>: subs   x9, x8, #0x2              ; =0x2 
        0x10010e7d8 <+148>: str    x9, [sp, #0x8]
        0x10010e7dc <+152>: b.eq   0x10010e7f4               ; <+176> at ViewController.m:27:13
        0x10010e7e0 <+156>: b      0x10010e800               ; <+188> at ViewController.m:30:1
        0x10010e7e4 <+160>: adrp   x0, 2
        0x10010e7e8 <+164>: add    x0, x0, #0x60             ; =0x60 
        0x10010e7ec <+168>: bl     0x10010eb2c               ; symbol stub for: NSLog
        0x10010e7f0 <+172>: b      0x10010e800               ; <+188> at ViewController.m:30:1
    ->  0x10010e7f4 <+176>: adrp   x0, 2
        0x10010e7f8 <+180>: add    x0, x0, #0x80             ; =0x80 
        0x10010e7fc <+184>: bl     0x10010eb2c               ; symbol stub for: NSLog
    

    上面代码中的0x10010e7d0里面的x8就代表着i,下一行的x9就代表i-2了,注意这行subs这个命令跟cmp一样也会影响到标志寄存器的(add这类操作命令都会),因此0x10010e7dc是通过i-2是否等于0来判断到底执行哪一段汇编代码。看上去跟if并没有太大区别。

    switch的汇编代码(多于3个case)

    下面试试将switch的case数增多,另外也让这些case变得不连续:

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSNumber *num = @(3);
        NSInteger i = num.integerValue;
        switch (i) {
            case 1:
                NSLog(@"num = 1");
                break;
                
            case 3:
                NSLog(@"num = 3");
                break;
                
            case 4:
                NSLog(@"num = 4");
                break;
            case 7:
                NSLog(@"num = 7");
                break;
    
        }
    }
    

    汇编代码跟刚才变得不一样了,从switch开始代码如下:

        0x1047be768 <+116>: subs   x0, x0, #0x1              ; =0x1 
        0x1047be76c <+120>: mov    x1, x0
        0x1047be770 <+124>: subs   x0, x0, #0x6              ; =0x6 
        0x1047be774 <+128>: str    x1, [sp, #0x8]
        0x1047be778 <+132>: str    x0, [sp]
        0x1047be77c <+136>: b.hi   0x1047be7d4               ; <+224> at ViewController.m:38:1
        0x1047be780 <+140>: adrp   x8, 0
        0x1047be784 <+144>: add    x8, x8, #0x7f4            ; =0x7f4 
        0x1047be788 <+148>: ldr    x9, [sp, #0x8]
        0x1047be78c <+152>: ldrsw  x10, [x8, x9, lsl #2]
        0x1047be790 <+156>: add    x8, x10, x8
        0x1047be794 <+160>: br     x8
        0x1047be798 <+164>: adrp   x0, 2
        0x1047be79c <+168>: add    x0, x0, #0x60             ; =0x60 
        0x1047be7a0 <+172>: bl     0x1047beb1c               ; symbol stub for: NSLog
        0x1047be7a4 <+176>: b      0x1047be7d4               ; <+224> at ViewController.m:38:1
        0x1047be7a8 <+180>: adrp   x0, 2
        0x1047be7ac <+184>: add    x0, x0, #0x80             ; =0x80 
        0x1047be7b0 <+188>: bl     0x1047beb1c               ; symbol stub for: NSLog
        0x1047be7b4 <+192>: b      0x1047be7d4               ; <+224> at ViewController.m:38:1
        0x1047be7b8 <+196>: adrp   x0, 2
        0x1047be7bc <+200>: add    x0, x0, #0xa0             ; =0xa0 
        0x1047be7c0 <+204>: bl     0x1047beb1c               ; symbol stub for: NSLog
        0x1047be7c4 <+208>: b      0x1047be7d4               ; <+224> at ViewController.m:38:1
        0x1047be7c8 <+212>: adrp   x0, 2
        0x1047be7cc <+216>: add    x0, x0, #0xc0             ; =0xc0 
        0x1047be7d0 <+220>: bl     0x1047beb1c               ; symbol stub for: NSLog
        0x1047be7d4 <+224>: add    x8, sp, #0x18  
    

    0x1047be768中,首先让x0减去1,这里的x0就是i,这里就是i-1的意思,注意到代码中的case是从1开始,其实这个1就是这个意思。从0x1047be770上看,x0又减去了6x0总共减了7,而代码中最大的是7,如果都命中看这些case,最后x0就是负数,而0x1047be77c中的b.hi是无符号大于的意思,在0x1047be770sub操作中如果不产生借位操作就会触发跳转。下面分三种情况分析一下:

    1. x0小于1,在0x1047be768的时候,x0已经变成了负数,由于这是无符号的,所以x0变成了特别大的数,不会在0x1047be770的运算中产生借位。
    2. x0大于7,在0x1047be770中,x0都不会产生借位
    3. x017之间,运算产生产生借位。
      前面两种都会在0x1047be77c中触发跳转,直接跳到0x1047be7d4跳出switch
      接下来再看看命中条件后的代码:
      0x1047be780中的adrp指的是让当前代码执行的地址出入后12位,再让最后的数左移12为相加后赋值给x8,比如这句的意思是:
    x8 = 0x1047be780&0xfffff000+0<12 = 0x1047be000
    

    结合0x1047be784,x8就变成了0x1047be7f4
    通过xcode中的debug->debug workflow->view memory查看0x1047be7f4的内存如下:

    A4 FF FF FF
    E0 FF FF FF
    B4 FF FF FF 
    C4 FF FF FF
    E0 FF FF FF
    E0 FF FF FF
    D4 FF FF FF
    

    可以看出这里存的头7个数都是负数,注意到第二行,第五行和第六行都是一样的,分别对应switch里面没有命中case的数字2,5,6。

    0x1047be788里面的x9[sp, #0x8]里面拿出值,而这个值在0x1047be774里面存过,其实就是x1,而这个x1x0-1获得的,也就是i-1

    0x1047be78c这一行代表从x8偏移x9左移2位的地址里面取值放到x10,左移2位就是乘以4的意思,也就是x8偏移x9*4个字节。比如这个case是3,我们拿到的就是FFFFFFB4(注意这是小端模式下,内存的数字得从右往左读)

    0x1047be7900x1047be794说明将要跳x8这个地址,而x8则是x10+x8,其实就是x8偏移了这个x10的地址,先打印下x10的int类型是:

    p (int)0xffffffffffffffb4
    (int) $4 = -76
    

    x80x1047be7f4,减去76(注意是10进制),得到0x1047be7a8,就是跳转的地址了。

    再看下如果switch中没有匹配的数字,即i = 2,5,6的时候,x10就等于FFFFFFE0,打印一下这个值的int类型是:

     p (int)0xFFFFFFE0 
    (int) $5 = -32
    

    x8减去32就得到0x1047be7d4,跟i>7或者i<1跳转的地方是一样的。

    switch的汇编代码(多于3个case,case跨度特别大的情况)

    下面改造一下代码,使得case间的跨度增加

    - (void)viewDidLoad {
        [super viewDidLoad];
        NSNumber *num = @(3);
        NSInteger i = num.integerValue;
        switch (i) {
            case 1:
                NSLog(@"num = 1");
                break;
                
            case 3:
                NSLog(@"num = 3");
                break;
                
            case 4:
                NSLog(@"num = 4");
                break;
            case 41:
                NSLog(@"num = 41");
                break;
    
        }
    }
    
    

    汇编代码如下:

        0x104306764 <+120>: subs   x0, x0, #0x1              ; =0x1 
        0x104306768 <+124>: str    x1, [sp, #0x28]
        0x10430676c <+128>: str    x0, [sp, #0x20]
        0x104306770 <+132>: b.eq   0x1043067b4               ; <+200> at ViewController.m:23:13
        0x104306774 <+136>: b      0x104306778               ; <+140> at ViewController.m:21:5
        0x104306778 <+140>: ldr    x8, [sp, #0x28]
        0x10430677c <+144>: subs   x9, x8, #0x3              ; =0x3 
        0x104306780 <+148>: str    x9, [sp, #0x18]
        0x104306784 <+152>: b.eq   0x1043067c4               ; <+216> at ViewController.m:27:13
        0x104306788 <+156>: b      0x10430678c               ; <+160> at ViewController.m:21:5
        0x10430678c <+160>: ldr    x8, [sp, #0x28]
        0x104306790 <+164>: subs   x9, x8, #0x4              ; =0x4 
        0x104306794 <+168>: str    x9, [sp, #0x10]
        0x104306798 <+172>: b.eq   0x1043067d4               ; <+232> at ViewController.m:31:13
        0x10430679c <+176>: b      0x1043067a0               ; <+180> at ViewController.m:21:5
        0x1043067a0 <+180>: ldr    x8, [sp, #0x28]
        0x1043067a4 <+184>: subs   x9, x8, #0x29             ; =0x29 
        0x1043067a8 <+188>: str    x9, [sp, #0x8]
        0x1043067ac <+192>: b.eq   0x1043067e4               ; <+248> at ViewController.m:34:13
        0x1043067b0 <+196>: b      0x1043067f0               ; <+260> at ViewController.m:38:1
        0x1043067b4 <+200>: adrp   x0, 2
        0x1043067b8 <+204>: add    x0, x0, #0x60             ; =0x60 
        0x1043067bc <+208>: bl     0x104306b1c               ; symbol stub for: NSLog
        0x1043067c0 <+212>: b      0x1043067f0               ; <+260> at ViewController.m:38:1
        0x1043067c4 <+216>: adrp   x0, 2
        0x1043067c8 <+220>: add    x0, x0, #0x80             ; =0x80 
        0x1043067cc <+224>: bl     0x104306b1c               ; symbol stub for: NSLog
        0x1043067d0 <+228>: b      0x1043067f0               ; <+260> at ViewController.m:38:1
        0x1043067d4 <+232>: adrp   x0, 2
        0x1043067d8 <+236>: add    x0, x0, #0xa0             ; =0xa0 
        0x1043067dc <+240>: bl     0x104306b1c               ; symbol stub for: NSLog
        0x1043067e0 <+244>: b      0x1043067f0               ; <+260> at ViewController.m:38:1
        0x1043067e4 <+248>: adrp   x0, 2
        0x1043067e8 <+252>: add    x0, x0, #0xc0             ; =0xc0 
        0x1043067ec <+256>: bl     0x104306b1c               ; symbol stub for: NSLog
        0x1043067f0 <+260>: sub    x8, x29, #0x28            ; =0x28 
    

    可以看出,基本变回了跟if差不多的汇编,这个测试过在跨度大于等于40的时候就是这样。

    总结

    switch语法在case超过3个且跨度少于40的时候,汇编会采用空间换时间的方式,提供一个表以作跳转的偏移量查询。在oc中switch通常和枚举一起使用,通常会满足这个条件。这种时候使用switch会稍微提升速度和稍稍增加包大小。不过这个优化比较微小,不算太必要。

    相关文章

      网友评论

          本文标题:if和switch在汇编中的区别

          本文链接:https://www.haomeiwen.com/subject/qwoqxhtx.html