iOS逆向实战--005:判断 & 循环 & 选择

2021-04-09  本文已影响0人  帅驼驼
判断

cmp:(Compare)比较指令

cmp把一个寄存器的内容和另一个寄存器的内容或立即数进行比较。但不存储结果,只是正确的更改标志

一般cmp做完判断后会进行跳转,后面通常会跟上b指令

有符号数

  • b.lt 标号:比价结果是小于(less than),执行标号,否则不跳转
  • b.le 标号:比较结果是小于等于(less than or qeual to),执行标号,否则不跳转
  • b.gt 标号:比较结果是大于(greater than),执行标号,否则不跳转
  • b.ge 标号:比较结果是大于等于(greater than or equal to),执行标号,否则不跳转
  • b.eq 标号:比较结果是等于(equal to),执行标号,否则不跳转
  • b.ne 标号:比较结果是不等于(not equal to),执行标号,否则不跳转

无符号数

  • b.lo 标号:比较结果是小于,执行标号,否则不跳转
  • b.ls 标号:比较结果是小于等于,执行标号,否则不跳转
  • b.hi 标号:比较结果是大于,执行标号,否则不跳转
  • b.hs 标号:比较结果是大于等于,执行标号,否则不跳转

案例:

编译器对if判断的识别

打开ViewController.m文件,写入以下代码:

#import "ViewController.h"

int g = 12;

void func(int a, int b){
   if (a > b) {
       g = a;
   } else {
       g = b;
   }
}

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   func(1, 2);
}

@end

使用真机编译项目,将Mach-O文件拖到Hopper中,找到func函数

  • func函数在Hopper中被分为四段

func函数,第一段

                    _func:
0000000100005f0c         sub        sp, sp, #0x10            ; CODE XREF=-[ViewController viewDidLoad]+28
0000000100005f10         str        w0, [sp, #0xc]
0000000100005f14         str        w1, [sp, #0x8]
0000000100005f18         ldr        w8, [sp, #0xc]
0000000100005f1c         ldr        w9, [sp, #0x8]
0000000100005f20         cmp        w8, w9
0000000100005f24         b.le       loc_100005f3c
  • 开辟栈空间,现场保护
  • cmp w8, w9:比较w8w9寄存器,相当于比较参数1参数2cmp指令相当于做减法,但只会影响状态寄存器,不会影响目标寄存器
  • b.le loc_100005f3c:如果参数1 - 参数2的结果<= 0,相当于触发else代码块,执行标号跳转到第三段。否则触发if代码块,向下执行第二段
  • 所以cmp指令之后的跳转,进入的是else代码分支

func函数,第二段。如果参数1 - 参数2 > 0执行此段代码

0000000100005f28         ldr        w8, [sp, #0xc]
0000000100005f2c         adrp       x9, #0x10000d000
0000000100005f30         add        x9, x9, #0x490           ; _g
0000000100005f34         str        w8, x9
0000000100005f38         b          loc_100005f4c
  • 此段代码,相当于if中的代码块
  • w8参数1x9为全局变量g,将w8写入x9
  • b指令,执行标号跳转到第四段

func函数,第三段。如果参数1 - 参数2 <= 0执行此段代码

                    loc_100005f3c:
0000000100005f3c         ldr        w8, [sp, #0x8]           ; CODE XREF=_func+24
0000000100005f40         adrp       x9, #0x10000d000
0000000100005f44         add        x9, x9, #0x490           ; _g
0000000100005f48         str        w8, x9
  • 此段代码,相当于else中的代码块
  • w8参数2x9为全局变量g,将w8写入x9
  • 继续向下执行第四段

func函数,第四段

                    loc_100005f4c:
0000000100005f4c         add        sp, sp, #0x10            ; CODE XREF=_func+44
0000000100005f50         ret
                       ; endp
  • 恢复栈平衡,返回
循环

不同的循环方式:

  • do...while
  • while
  • for

案例1:

编译器对do...while循环的识别

打开ViewController.m文件,写入以下代码:

#import "ViewController.h"

void funcA(){

   int sum = 0;
   int i = 0;
   
   do {
       sum += 1;
       i++;
   } while (i < 100);
}

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   funcA();
}

@end

使用真机编译项目,将Mach-O文件拖到Hopper中,找到funcA函数

  • funcA函数在Hopper中被分为三段

funcA函数,第一段

                    _funcA:
0000000100005f24         sub        sp, sp, #0x10            ; CODE XREF=-[ViewController viewDidLoad]+20
0000000100005f28         str        wzr, [sp, #0xc]
0000000100005f2c         str        wzr, [sp, #0x8]
  • 开辟栈空间,现场保护。将wzr寄存器的值,分别在sp + 0xcsp + 0x8地址入栈
  • 继续向下执行第二段

funcA函数,第二段

                    loc_100005f30:
0000000100005f30         ldr        w8, [sp, #0xc]             ; CODE XREF=_funcA+44
0000000100005f34         add        w8, w8, #0x1
0000000100005f38         str        w8, [sp, #0xc]
0000000100005f3c         ldr        w8, [sp, #0x8]
0000000100005f40         add        w8, w8, #0x1
0000000100005f44         str        w8, [sp, #0x8]
0000000100005f48         ldr        w8, [sp, #0x8]
0000000100005f4c         cmp        w8, #0x64
0000000100005f50         b.lt       loc_100005f30
  • 因为使用do...while循环,所以cmpb.lt指令在最下面。这意味着此代码块,至少也会执行一次
  • 读取sp + 0xc地址的值,写入w8,进行w8 += 1操作,将结果再次入栈
  • 读取sp + 0x8地址的值,写入w8,进行w8 += 1操作,将结果再次入栈
  • 再次将sp + 0x8地址的值,写入w8
  • cmp指令,将w8的值和#0x64,即10进制100进行比较
  • b.lt指令,如果w8 - 100 < 0,执行标号跳转到第二段,相当于递归执行。否则向下执行第三段,相当于跳出循环

funcA函数,第三段

0000000100005f54         add        sp, sp, #0x10
0000000100005f58         ret
                       ; endp
  • 恢复栈平衡,返回

案例2:

编译器对while循环的识别

打开ViewController.m文件,写入以下代码:

#import "ViewController.h"

void funcA(){

   int sum = 0;
   int i = 0;
 
   while (i < 100) {
       sum += 1;
       i++;
   }
}

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   funcA();
}

@end

使用真机编译项目,将Mach-O文件拖到Hopper中,找到funcA函数

  • funcA函数在Hopper中被分为四段

funcA函数,第一段

                    _funcA:
0000000100005f20         sub        sp, sp, #0x10              ; CODE XREF=-[ViewController viewDidLoad]+20
0000000100005f24         str        wzr, [sp, #0xc]
0000000100005f28         str        wzr, [sp, #0x8]
  • 开辟栈空间,现场保护。将wzr寄存器的值,分别在sp + 0xcsp + 0x8地址入栈
  • 继续向下执行第二段

funcA函数,第二段

                    loc_100005f2c:
0000000100005f2c         ldr        w8, [sp, #0x8]              ; CODE XREF=_funcA+48
0000000100005f30         cmp        w8, #0x64
0000000100005f34         b.ge       loc_100005f54
  • 读取sp + 0x8地址的值,写入w8
  • cmp指令,将w8的值和#0x64,即10进制100进行比较
  • b.lt指令,如果w8 - 100 >= 0,执行标号跳转到第四段,相当于跳出循环。否则向下执行第三段,执行循环内部的代码块

funcA函数,第三段

0000000100005f38         ldr        w8, [sp, #0xc]
0000000100005f3c         add        w8, w8, #0x1
0000000100005f40         str        w8, [sp, #0xc]
0000000100005f44         ldr        w8, [sp, #0x8]
0000000100005f48         add        w8, w8, #0x1
0000000100005f4c         str        w8, [sp, #0x8]
0000000100005f50         b          loc_100005f2c
  • 读取sp + 0xc地址的值,写入w8,进行w8 += 1操作,将结果再次入栈
  • 读取sp + 0x8地址的值,写入w8,进行w8 += 1操作,将结果再次入栈
  • b指令,执行标号跳转到第二段,继续验证进入循环的条件限制,决定之后的代码流程

funcA函数,第四段

                    loc_100005f54:
0000000100005f54         add        sp, sp, #0x10               ; CODE XREF=_funcA+20
0000000100005f58         ret
                       ; endp
  • 恢复栈平衡,返回

案例3:

编译器对for循环的识别

打开ViewController.m文件,写入以下代码:

#import "ViewController.h"

void funcA(){

   int sum = 0;
   
   for (int i = 0; i < 100; i++) {
       sum += 1;
   }
}

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   funcA();
}

@end

使用真机编译项目,将Mach-O文件拖到Hopper中,找到funcA函数

  • funcA函数在Hopper中被分为四段
  • 代码流程和while循环完全一致
选择

switch语句:当满足条件时,执行某些操作。可以使用if-else来实现,也可以使用switch来实现

  • 假设switch语句的分支比较少的时候,例如:少于4个的时候,没有必要使用此结构,此时switch的执行方式相当于if-else
  • 在分支比较多的时候,编译时会生成一张数据表。表中每个偏移值占四字节,通过查表找到指定代码的标号地址,无需遍历。以空间换取时间,提高效率
  • 各个分支常量的差值较大的时候,编译器会在效率和内存中进行取舍,这个时候编译器有可能会编译成类似于if-else的结构

案例1:

switch语句的分支比较少的时候,编译器会如何处理?

打开ViewController.m文件,写入以下代码:

#import "ViewController.h"

void funcA(int a){
   switch (a) {
       case 1:
           printf("打坐");
           break;
       case 2:
           printf("加红");
           break;
       case 3:
           printf("加蓝");
           break;
       default:
           printf("待命");
           break;
   }
}

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   funcA(1);
}

@end

使用真机编译项目,将Mach-O文件拖到Hopper中,找到funcA函数

  • funcA函数在Hopper中被分为十一段

funcA函数,第一段

                    _funcA:
0000000100005e9c         sub        sp, sp, #0x20               ; CODE XREF=-[ViewController viewDidLoad]+24
0000000100005ea0         stp        x29, x30, [sp, #0x10]
0000000100005ea4         add        x29, sp, #0x10
0000000100005ea8         stur       w0, [x29, #-0x4]
0000000100005eac         ldur       w8, [x29, #-0x4]
0000000100005eb0         cmp        w8, #0x1
0000000100005eb4         str        w8, [sp, #0x8]
0000000100005eb8         b.eq       loc_100005ee0
  • 开辟栈空间,现场保护
  • w0寄存器的值,在x29 - 0x4地址入栈
  • 读取x29 - 0x4地址的值,写入w8
  • cmp指令,将w8的值和#0x1,即10进制1进行比较
  • w8寄存器的值,在sp + 0x8地址入栈
  • b.eq指令,如果w8 - 1 == 0,执行标号跳转到第七段,相当于进入case 1的代码块。否则向下执行第二段

funcA函数,第二段

0000000100005ebc         b          loc_100005ec0
  • b指令,执行标号跳转到第三段

funcA函数,第三段

                    loc_100005ec0:
0000000100005ec0         ldr        w8, [sp, #0x8]                ; CODE XREF=_funcA+32
0000000100005ec4         cmp        w8, #0x2
0000000100005ec8         b.eq       loc_100005ef0
  • 读取sp + 0x8地址的值,写入w8
  • cmp指令,将w8的值和#0x2,即10进制2进行比较
  • b.eq指令,如果w8 - 2 == 0,执行标号跳转到第八段,相当于进入case 2的代码块。否则向下执行第四段

funcA函数,第四段

0000000100005ecc         b          loc_100005ed0
  • b指令,执行标号跳转到第五段

funcA函数,第五段

                    loc_100005ed0:
0000000100005ed0         ldr        w8, [sp, #0x8]                ; CODE >XREF=_funcA+48
0000000100005ed4         cmp        w8, #0x3
0000000100005ed8         b.eq       loc_100005f00
  • 读取sp + 0x8地址的值,写入w8
  • cmp指令,将w8的值和#0x3,即10进制3进行比较
  • b.eq指令,如果w8 - 3 == 0,执行标号跳转到第九段,相当于进入case 3的代码块。否则向下执行第六段

funcA函数,第六段

0000000100005edc         b          loc_100005f10
  • b指令,执行标号跳转到第十段,相当于进入default的代码块

funcA函数,第七段。即:case 1的代码块

                    loc_100005ee0:
0000000100005ee0         adrp       x0, #0x100006000                ; argument #1 for method imp___stubs__printf, CODE XREF=_funcA+28
0000000100005ee4         add        x0, x0, #0x634                  ; "\\xE6\\x89\\x93\\xE5\\x9D\\x90"
0000000100005ee8         bl         imp___stubs__printf
0000000100005eec         b          loc_100005f1c
  • 在常量区拿到字符串打坐,写入x0
  • 调用printf函数
  • b指令,执行标号跳转到第十一段。即:funcA函数的结尾处

funcA函数,第八段。即:case 2的代码块

                    loc_100005ef0:
0000000100005ef0         adrp       x0, #0x100006000                ; argument #1 for method imp___stubs__printf, CODE XREF=_funcA+44
0000000100005ef4         add        x0, x0, #0x63b                  ; "\\xE5\\x8A\\xA0\\xE7\\xBA\\xA2"
0000000100005ef8         bl         imp___stubs__printf
0000000100005efc         b          loc_100005f1c
  • 在常量区拿到字符串加红,写入x0
  • 调用printf函数
  • b指令,执行标号跳转到第十一段。即:funcA函数的结尾处

funcA函数,第九段。即:case 3的代码块

                    loc_100005f00:
0000000100005f00         adrp       x0, #0x100006000                 ; argument #1 for method imp___stubs__printf, CODE XREF=_funcA+60
0000000100005f04         add        x0, x0, #0x642                   ; "\\xE5\\x8A\\xA0\\xE8\\x93\\x9D"
0000000100005f08         bl         imp___stubs__printf
0000000100005f0c         b          loc_100005f1c
  • 在常量区拿到字符串加蓝,写入x0
  • 调用printf函数
  • b指令,执行标号跳转到第十一段。即:funcA函数的结尾处

funcA函数,第十段。即:default的代码块

                    loc_100005f10:
0000000100005f10         adrp       x0, #0x100006000                  ; argument #1 for method imp___stubs__printf, CODE XREF=_funcA+64
0000000100005f14         add        x0, x0, #0x649                    ; "\\xE5\\xBE\\x85\\xE5\\x91\\xBD"
0000000100005f18         bl         imp___stubs__printf
  • 在常量区拿到字符串待命,写入x0
  • 调用printf函数
  • 继续向下执行第十一段

funcA函数,第十一段

                    loc_100005f1c:
0000000100005f1c         ldp        x29, x30, [sp, #0x10]            ; CODE XREF=_funcA+80, _funcA+96, _funcA+112
0000000100005f20         add        sp, sp, #0x20
0000000100005f24         ret
                       ; endp
  • 还原x29x30的值
  • 恢复栈平衡
  • 返回

上述案例中,每个case都会检查一次,当所有case都不满足时,跳转default。从代码流程上,感觉和使用if-else实现没有区别

funcA函数,使用if-else实现:

void funcA(int a){
   if(a == 1){
       printf("打坐");
   }
   else if (a == 2){
       printf("加红");
   }
   else if (a == 3){
       printf("加蓝");
   }
   else{
       printf("待命");
   }
}

使用真机编译项目,将Mach-O文件拖到Hopper中,找到funcA函数

  • 从流程上看,比switch语句少了一些b指令的中间跳转,但也是每个条件分支都会检查一次,当所有if都不满足时,跳转else

结论:当switch语句的分支比较少的时候,例如:少于4个的时候,没有必要使用此结构,此时switch的执行方式相当于if-else

案例2:

在分支比较多的时候,如果case的值是连续的,或者间隔较小,编译时会生成一张数据表

打开ViewController.m文件,写入以下代码:

#import "ViewController.h"

void funcA(int a){
   switch (a) {
       case 1:
           printf("打坐");
           break;
       case 2:
           printf("加红");
           break;
       case 3:
           printf("加蓝");
           break;
       case 4:
           printf("打怪");
           break;
       default:
           printf("待命");
           break;
   }
}

@implementation ViewController

- (void)viewDidLoad {
//    [super viewDidLoad];
   funcA(4);
}

@end

真机运行项目,来到func方法

  • funcA函数的汇编代码分为八段

funcA函数,第一段

   0x102269ec4 <+0>:   sub    sp, sp, #0x20             ; =0x20 
   0x102269ec8 <+4>:   stp    x29, x30, [sp, #0x10]
   0x102269ecc <+8>:   add    x29, sp, #0x10            ; =0x10 
   0x102269ed0 <+12>:  stur   w0, [x29, #-0x4]
   0x102269ed4 <+16>:  ldur   w8, [x29, #-0x4]
   0x102269ed8 <+20>:  subs   w8, w8, #0x1              ; =0x1 
   0x102269edc <+24>:  mov    x9, x8
   0x102269ee0 <+28>:  ubfx   x9, x9, #0, #32
   0x102269ee4 <+32>:  cmp    x9, #0x3                  ; =0x3 
   0x102269ee8 <+36>:  str    x9, [sp]
   0x102269eec <+40>:  b.hi   0x102269f48               ; <+132> at ViewController.m
  • 开辟栈空间,现场保护
  • w0x29 - 0x4地址入栈,然后再寻址写入w8
  • subs w8, w8, #0x1w8为当前变量的值,减去#0x1,写入w8寄存器
    w8viewDidLoad方法中,调用funcA函数,传入的参数为4
    #0x1:最小case的值,转为10进制1
    w8 -= 1,即:4 - 1 ,得到参数区间值3
    使用subs指令,影响目标寄存器,同时影响状态寄存器
  • x8写入x9
  • ubfx x9, x9, #0, #32:保留x9寄存器的低32位,剩余高位用0填充
    ubfx指令:从x9寄存器的第0位开始,提取32位x9寄存器,剩余高位用0填充
    例如:x90x0123456789abcdef,此指令执行后,x90x0000000089abcdef
  • cmp x9, #0x3:将x9的值和#0x3进行比较
    x9参数区间值
    #0x3:最大case值减去最小case值,得到case区间值3
  • x9sp所在位置入栈
  • b.hi 0x104105f48:如果参数区间值 - case区间值 > 0,执行标号跳转到第七段,相当于进入default的代码块。否则向下执行第二段

funcA函数,第二段。通过查表,获取跳转到指定casedefault的代码标号

   0x102269ef0 <+44>:  adrp   x8, 0
   0x102269ef4 <+48>:  add    x8, x8, #0xf60            ; =0xf60 
   0x102269ef8 <+52>:  ldr    x11, [sp]
   0x102269efc <+56>:  ldrsw  x10, [x8, x11, lsl #2]
   0x102269f00 <+60>:  add    x9, x8, x10
   0x102269f04 <+64>:  br     x9
  • adrp x8, 0 ~ add x8, x8, #0xf60:将0x102269f60地址,写入x8
    0x102269f60:数据表的首地址,存储在funcA函数地址之后
    funcA函数最后指令地址:0x102269f5c,向后偏移4字节0x102269f60
  • 读取sp地址的值,写入x11
  • ldrsw x10, [x8, x11, lsl #2]
    ldrsw指令:通过x8 + x11左移2位计算地址,读取该地址的值,赋值给x10
    x8:值为0x102269f60。数据表首地址
    x11:值为3参数 - 最小case值,得到的参数区间值
    x11左移2位3 << 2 = 12,即:16进制0xc
    数据表首地址加上偏移地址:0x102269f60 + 0xc = 0x102269f6c
    读取0x102269f6c地址的值0xffffffffffffffd8,写入x10

  • add x9, x8, x10:将x8x10相加,写入x9
    x8:值为0x102269f60。数据表首地址
    x10:值为0xffffffffffffffd8,即:-4016进制0x-28。从数据表中获取距代码标号的偏移值
    0x102269f60 + 0x-28 = 0x102269f38,写入x9
  • br x9:执行x9寄存器所存储的标号
    x9:值为0x102269f38。执行标号跳转到第六段,相当于进入case 4的代码块

funcA函数,第三段。即:case 1的代码块

   0x102269f08 <+68>:  adrp   x0, 1
   0x102269f0c <+72>:  add    x0, x0, #0x634            ; =0x634 
   0x102269f10 <+76>:  bl     0x10226a598               ; symbol stub for: printf
   0x102269f14 <+80>:  b      0x102269f54               ; <+144> at ViewController.m:28:1
  • 在常量区拿到字符串打坐,写入x0
  • 调用printf函数
  • b指令,执行标号跳转到第八段。即:funcA函数的结尾处

funcA函数,第四段。即:case 2的代码块

   0x102269f18 <+84>:  adrp   x0, 1
   0x102269f1c <+88>:  add    x0, x0, #0x63b            ; =0x63b 
   0x102269f20 <+92>:  bl     0x10226a598               ; symbol stub for: printf
   0x102269f24 <+96>:  b      0x102269f54               ; <+144> at ViewController.m:28:1
  • 在常量区拿到字符串加红,写入x0
  • 调用printf函数
  • b指令,执行标号跳转到第八段。即:funcA函数的结尾处

funcA函数,第五段。即:case 3的代码块

   0x102269f28 <+100>: adrp   x0, 1
   0x102269f2c <+104>: add    x0, x0, #0x642            ; =0x642 
   0x102269f30 <+108>: bl     0x10226a598               ; symbol stub for: printf
   0x102269f34 <+112>: b      0x102269f54               ; <+144> at ViewController.m:28:1
  • 在常量区拿到字符串加蓝,写入x0
  • 调用printf函数
  • b指令,执行标号跳转到第八段。即:funcA函数的结尾处

funcA函数,第六段。即:case 4的代码块

   0x102269f38 <+116>: adrp   x0, 1
   0x102269f3c <+120>: add    x0, x0, #0x649            ; =0x649 
   0x102269f40 <+124>: bl     0x10226a598               ; symbol stub for: printf
   0x102269f44 <+128>: b      0x102269f54               ; <+144> at ViewController.m:28:1
  • 在常量区拿到字符串打怪,写入x0
  • 调用printf函数
  • b指令,执行标号跳转到第八段。即:funcA函数的结尾处

funcA函数,第七段。即:default的代码块

   0x102269f48 <+132>: adrp   x0, 1
   0x102269f4c <+136>: add    x0, x0, #0x650            ; =0x650 
   0x102269f50 <+140>: bl     0x10226a598               ; symbol stub for: printf
  • 在常量区拿到字符串待命,写入x0
  • 调用printf函数
  • 继续向下执行第八段

funcA函数,第八段

   0x102269f54 <+144>: ldp    x29, x30, [sp, #0x10]
   0x102269f58 <+148>: add    sp, sp, #0x20             ; =0x20 
   0x102269f5c <+152>: ret    
  • 还原x29x30的值
  • 恢复栈平衡
  • 返回

上述案例中,switch语句生成的汇编代码逻辑

【第一步】:判断参数值是否在case值范围之内

  • 通过参数值 - 最小case值,得到参数区间值
  • 通过最大case值 - 最小case值,得到case区间值
  • 参数区间值case区间值进行比较
  • 如果参数区间值 > case区间值,表示超出case范围,直接跳转到default代码标号。否则进入【第二步】

【第二步】:通过查表,获取跳转到指定casedefault的代码标号

  • 获取数据表首地址
  • 计算数据表首地址加上参数区间值偏移x位后的地址
  • 通过计算后地址,从数据表中获取距代码标号的偏移值
  • 通过数据表首地址 + 16进制(偏移值),计算出指定casedefault的代码标号
  • 虽然参数在case值范围之内,也有可能进入default分支,因为case值不一定是连续的
  • 表中存储的数据量,是case区间值 + default的总数。所以case的值间隔越大,需要存储的数据量就会越多
  • 表中存储的数据,以表头地址为参照,计算出跳转到各分支代码的偏移值。它们在编译时已经被编译器计算好并存储在表中,因为switch的条件分支代码是连续的,因此可以这样计算
  • 数据表中每四字节存储一个偏移值,所以在ldrsw指令中有左移2位的计算
  • 由于ASLR技术(Address Spce Layout Randomization),翻译过来就是地址空间布局随机化。数据表中不直接存储地址,而是存储偏移值

结论:在switch语句分支比较多的时候,如果case的值是连续的,或间隔较小,编译器会建立一张数据表。通过查表直接获取跳转到指定代码的标号地址,无需遍历每一个case分支,以空间换取时间

案例3:

case常量的差值较大时,编译器会如何处理?

打开ViewController.m文件,写入以下代码:

void funcA(int a){
   switch (a) {
       case 1:
           printf("打坐");
           break;
       case 20:
           printf("加红");
           break;
       case 40:
           printf("加蓝");
           break;
       case 80:
           printf("打怪");
           break;
       default:
           printf("待命");
           break;
   }
}

使用真机编译项目,将Mach-O文件拖到Hopper中,找到funcA函数

  • 如果case值的间隔较大,编译器会权衡是否还要以空间换取时间。上述案例中,如果建立数据表需要存储大量数据,耗费大量空间,此时编译器会选择使用if-else的执行方式

结论:各个分支常量的差值较大的时候,编译器会在效率和内存中进行取舍,这个时候编译器有可能会编译成类似于if-else的结构

总结

判断(if)

  • cmp:比较指令,其实在做减法。不影响目标寄存器,只影响状态寄存器

循环

  • do…while循环:判断条件在后面,满足循环条件往外跳
  • while循环:判断条件在前面,满足循环条件往里跳
  • for循环:和while循环逻辑一致

选择

  • 分支少于4个switch底层代码和if-else一样
  • 在分支比较多的时候,编译时会生成一张数据表,通过查表进行代码调转,无需遍历。以空间换取时间,提高效率
  • 各个分支常量差值较大,底层代码可能使用数据表,也可能使用if-else。因为编译器会在效率和内存中进行取舍
上一篇下一篇

猜你喜欢

热点阅读