04 - ADRP指令&cmp指令&switch汇编

ADRP指令

内存分为以下几大区:
代码区:存放代码的,是可读可执行的。
栈区:存放参数、局部变量、临时数据的,是可读可写的。
堆区:动态申请的,是可读可写的。
全局区:存放全局变量的,是可读可写的。
常量区:存放常量,是只读的。

这里我们重点看一下全局区常量区的值在内存中是如何存储的。
假设我们有如下一段代码,定义一个全局变量g=12,定义一个常量f="hello"

int g = 12;
void funcA(int a,int b){
    const char *f="hello";
    g = a+b;
}
int main(int argc, char * argv[]) {
    funcA(1,2);
    return 0;
}

这段代码的汇编如下:

Demo`funcA:
->  Demo`funcA:
->  0x104ece208 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    //这是funcA函数的第一个参数
    0x104ece20c <+4>:  str    w0, [sp, #0xc]
    //这是funcA函数的第二个参数
    0x104ece210 <+8>:  str    w1, [sp, #0x8]
    //获取字符串常量
    0x104ece214 <+12>: adrp   x8, 1
    0x104ece218 <+16>: add    x8, x8, #0xf55            ; =0xf55 
    0x104ece21c <+20>: str    x8, [sp]
    //w9 = 入参 a
    0x104ece220 <+24>: ldr    w9, [sp, #0xc]
    //w10 = 入参 b
    0x104ece224 <+28>: ldr    w10, [sp, #0x8]
    0x104ece228 <+32>: add    w9, w9, w10
    //获取全局变量g的地址
    0x104ece22c <+36>: adrp   x8, 3
    0x104ece230 <+40>: add    x8, x8, #0x5b0            ; =0x5b0 
    //将a+b的结果存入全局变量的地址中
    0x104ece234 <+44>: str    w9, [x8]
    0x104ece238 <+48>: add    sp, sp, #0x10             ; =0x10 
    0x104ece23c <+52>: ret 

以上代码中,在获取字符串常量和全局变量时,出现了一个新的指令ADRPADRP:这是一条小范围的地址读取指令,它将基于PC寄存器相对偏移的地址读到目标寄存器中;

在使用ADRP计算内存地址之前,需要理解一个概念:内存分页机制

内存分页机制:将虚拟内存空间物理内存空间划分为大小相同的页,并以作为内存空间划分的最小单位。空间增长也容易实现:只需要分配额外的虚拟页面,并找到一个闲置的物理页面存放即可。一个内存页大小为PAGE_SIZE字节,在不同的平台PAGE_SIZE不同。例如:Mac OS中,在终端通过PAGESIZE命令可获取内存页大小为:4096(4K)。而在Aarch64 (Arm64) Linux 系统上的内存页配置经常是64KB

在分页系统的机制下:一个程序发出的虚拟地址由两部分组成:页面号页内偏移值

接下来我们来解释一下字符串常量的ADRP指令

0x104ece214 <+12>: adrp   x8, 1
0x104ece218 <+16>: add    x8, x8, #0xf55            ; =0xf55 

这两句指令的意思:

  1. 根据页面号,将x8寄存器指向页面号对应的内存页。
  2. 再将进行页内偏移值,找到目标虚拟地址。

根据以上解释,我们通过手动计算x8的地址。

  1. 计算当前pc寄存器指向地址所处的内存页,由于内存页大小为4K(0x1000),因此将其低12位置为0即为结果,结果为:0x104ece000,该页的寻址范围为:0x104ece000 - 0x104ecefff
  2. 偏移至1号内存页:x8 = 0x104ece000 + 1*4096 = 0x104ecf000
  3. 页内偏移:x8 = 0x104ecf000 + 0xf55 = 0x104ecff55

通过lldb,来查看一下0x104ecff55地址中的内容

(lldb) x 0x104ecff55
0x104ecff55: 68 65 6c 6c 6f 00 54 40 22 55 49 57 69 6e 64 6f  hello.T@"UIWindo
0x104ecff65: 77 22 2c 26 2c 4e 2c 56 5f 77 69 6e 64 6f 77 00  w",&,N,V_window.

由lldb可以看到,该地址存储的就是hello的字符串常量

接下来,看一下全局变量gADRP指令

//获取全局变量g的地址
0x104ece22c <+36>: adrp   x8, 3
0x104ece230 <+40>: add    x8, x8, #0x5b0            ; =0x5b0 
  1. 计算当前pc寄存器指向地址所处的内存页,由于内存页大小为4K(0x1000),因此将其低12位置为0即为结果,结果为:0x104ece000,该页的寻址范围为:0x104ece000 - 0x104ecefff
  2. 偏移至3号内存页:x8 = 0x104ece000 + 3*4096 = 0x104ed1000
  3. 页内偏移:x8 = 0x104ed1000 + 0x5b0 = 0x104ed15b0

由lldb可以看到,该地址当前存储的就是定义的全局变量g=12。

(lldb) x 0x104ed15b0
0x104ed15b0: 0c 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
0x104ed15c0: be f3 ec 04 01 00 00 00 48 0f ed 04 01 00 00 00  ........H.......

循环语句

常见的循环有:for循环do/while循环while循环
接下来分别使用三个案例来查看一下这三种循环使用的指令。

for循环

void funcA(int a,int b){
    for(int i = 0; i< 2; i++){
        a += b;
    }
}
//其汇编如下:
Demo`funcA:
    0x104e061f0 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x104e061f4 <+4>:  str    w0, [sp, #0xc]
    0x104e061f8 <+8>:  str    w1, [sp, #0x8]
->  0x104e061fc <+12>: str    wzr, [sp, #0x4]
    0x104e06200 <+16>: ldr    w8, [sp, #0x4]
    0x104e06204 <+20>: cmp    w8, #0x2                  ; =0x2 
    0x104e06208 <+24>: b.ge   0x104e0622c               ; <+60> at main.m:53:1
    0x104e0620c <+28>: ldr    w8, [sp, #0x8]
    0x104e06210 <+32>: ldr    w9, [sp, #0xc]
    0x104e06214 <+36>: add    w8, w9, w8
    0x104e06218 <+40>: str    w8, [sp, #0xc]
    0x104e0621c <+44>: ldr    w8, [sp, #0x4]
    0x104e06220 <+48>: add    w8, w8, #0x1              ; =0x1 
    0x104e06224 <+52>: str    w8, [sp, #0x4]
    0x104e06228 <+56>: b      0x104e06200               ; <+16> at main.m:50:20
    0x104e0622c <+60>: add    sp, sp, #0x10             ; =0x10 
    0x104e06230 <+64>: ret    

这部分汇编主要是完成以下工作:
【步骤1】开辟函数栈
【步骤2】将w8(初始为0)与0x2进行比较,若w8大于等于2,则跳转至0x104e0622c【步骤5】
【步骤3】若w8小于2,则执行for循环语句块的内容。即{a += b;}
【步骤4】将w8+1后,跳转至0x104e06200【步骤2】
【步骤5】释放函数栈

do/while循环

void funcA(int a,int b){
    int i = 0;
    do{
        a +=b;
        i++;
    }while (i<2);
}
//其汇编如下:
Demo`funcA:
    0x1007ca1f4 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x1007ca1f8 <+4>:  str    w0, [sp, #0xc]
    0x1007ca1fc <+8>:  str    w1, [sp, #0x8]
    0x1007ca200 <+12>: str    wzr, [sp, #0x4]
->  0x1007ca204 <+16>: ldr    w8, [sp, #0x8]
    0x1007ca208 <+20>: ldr    w9, [sp, #0xc]
    0x1007ca20c <+24>: add    w8, w9, w8
    0x1007ca210 <+28>: str    w8, [sp, #0xc]
    0x1007ca214 <+32>: ldr    w8, [sp, #0x4]
    0x1007ca218 <+36>: add    w8, w8, #0x1              ; =0x1 
    0x1007ca21c <+40>: str    w8, [sp, #0x4]
    0x1007ca220 <+44>: ldr    w8, [sp, #0x4]
    0x1007ca224 <+48>: cmp    w8, #0x2                  ; =0x2 
    0x1007ca228 <+52>: b.lt   0x1007ca204               ; <+16> at main.m:52:13
    0x1007ca22c <+56>: add    sp, sp, #0x10             ; =0x10 
    0x1007ca230 <+60>: ret    

这部分汇编主要是完成以下工作:
【步骤1】开辟函数栈
【步骤2】执行do语句块内容,即{a += b;i++;}
【步骤3】将w8(初始为0)与0x2进行比较,若w8小于2,则跳转至0x1007ca210【步骤2】
【步骤4】若w8大于等于2,则继续向下执行,即跳出循环,释放函数栈

while循环

void funcA(int a,int b){
    int i = 0;
    while(i<2){
        a +=b;
        i++;
    }
}
//其汇编如下:
Demo`funcA:
    0x100efa1f0 <+0>:  sub    sp, sp, #0x10             ; =0x10 
    0x100efa1f4 <+4>:  str    w0, [sp, #0xc]
    0x100efa1f8 <+8>:  str    w1, [sp, #0x8]
->  0x100efa1fc <+12>: str    wzr, [sp, #0x4]
    0x100efa200 <+16>: ldr    w8, [sp, #0x4]
    0x100efa204 <+20>: cmp    w8, #0x2                  ; =0x2 
    0x100efa208 <+24>: b.ge   0x100efa22c               ; <+60> at main.m:55:1
    0x100efa20c <+28>: ldr    w8, [sp, #0x8]
    0x100efa210 <+32>: ldr    w9, [sp, #0xc]
    0x100efa214 <+36>: add    w8, w9, w8
    0x100efa218 <+40>: str    w8, [sp, #0xc]
    0x100efa21c <+44>: ldr    w8, [sp, #0x4]
    0x100efa220 <+48>: add    w8, w8, #0x1              ; =0x1 
    0x100efa224 <+52>: str    w8, [sp, #0x4]
    0x100efa228 <+56>: b      0x100efa200               ; <+16> at main.m:51:11
    0x100efa22c <+60>: add    sp, sp, #0x10             ; =0x10 
    0x100efa230 <+64>: ret    

这部分汇编主要是完成以下工作:
【步骤1】开辟函数栈
【步骤2】将w8(初始为0)与0x2进行比较,若w8大于等于2,则跳转至0x100efa22c【步骤5】
【步骤3】若w8小于2,则执行for循环语句块的内容。即{a += b;i++;}
【步骤4】将【步骤3】执行完后,跳转至0x100efa200【步骤2】
【步骤5】释放函数栈

循环语句汇编指令总结

  • for循环while循环在汇编层面看是完全一样的。
  • do/while循环需要先执行函数体,再进行条件判断。

switch语句

情况1:case小于4个的情况

首先,我们写个简单的switch语句,来看一下它的汇编是怎样的。

void funcA(int a,int b){
    int c = 0;
    switch(a){
        case 1:
            c += 1;
            break;
        case 2:
            c += 2;
            break;
        case 3:
            c += 3;
            break;
        default:
            c = b;
            break;
    }
}
//其汇编如下:
Demo`funcA:
    0x104e861b0 <+0>:   sub    sp, sp, #0x10             ; =0x10 
    0x104e861b4 <+4>:   str    w0, [sp, #0xc]
    0x104e861b8 <+8>:   str    w1, [sp, #0x8]
    0x104e861bc <+12>:  str    wzr, [sp, #0x4]
->  0x104e861c0 <+16>:  ldr    w8, [sp, #0xc]
    0x104e861c4 <+20>:  cmp    w8, #0x1                  ; =0x1 
    0x104e861c8 <+24>:  str    w8, [sp]
    0x104e861cc <+28>:  b.eq   0x104e861f4               ; <+68> at main.m:53:15
    0x104e861d0 <+32>:  b      0x104e861d4               ; <+36> at main.m
    0x104e861d4 <+36>:  ldr    w8, [sp]
    0x104e861d8 <+40>:  cmp    w8, #0x2                  ; =0x2 
    0x104e861dc <+44>:  b.eq   0x104e86204               ; <+84> at main.m:56:15
    0x104e861e0 <+48>:  b      0x104e861e4               ; <+52> at main.m
    0x104e861e4 <+52>:  ldr    w8, [sp]
    0x104e861e8 <+56>:  cmp    w8, #0x3                  ; =0x3 
    0x104e861ec <+60>:  b.eq   0x104e86214               ; <+100> at main.m:59:15
    0x104e861f0 <+64>:  b      0x104e86224               ; <+116> at main.m:62:17
    0x104e861f4 <+68>:  ldr    w8, [sp, #0x4]
    0x104e861f8 <+72>:  add    w8, w8, #0x1              ; =0x1 
    0x104e861fc <+76>:  str    w8, [sp, #0x4]
    0x104e86200 <+80>:  b      0x104e8622c               ; <+124> at main.m:65:1
    0x104e86204 <+84>:  ldr    w8, [sp, #0x4]
    0x104e86208 <+88>:  add    w8, w8, #0x2              ; =0x2 
    0x104e8620c <+92>:  str    w8, [sp, #0x4]
    0x104e86210 <+96>:  b      0x104e8622c               ; <+124> at main.m:65:1
    0x104e86214 <+100>: ldr    w8, [sp, #0x4]
    0x104e86218 <+104>: add    w8, w8, #0x3              ; =0x3 
    0x104e8621c <+108>: str    w8, [sp, #0x4]
    0x104e86220 <+112>: b      0x104e8622c               ; <+124> at main.m:65:1
    0x104e86224 <+116>: ldr    w8, [sp, #0x8]
    0x104e86228 <+120>: str    w8, [sp, #0x4]
    0x104e8622c <+124>: add    sp, sp, #0x10             ; =0x10 
    0x104e86230 <+128>: ret    

从汇编来看,switch语句主要分为三步

  • 通过CMP指令依次与switch语句中的Case标号进行比较。
  • 等于case标号时,则跳转至Case标号对应的语句块中执行。
  • 当执行完Case语句块的内容后,使用b指令跳出switch语句。

情况2:Case大于4个,且标号差值较小的情况

从以上的分析中得出switch语句在汇编层面看,与if语句一样。但是不是所有的switch语句都是这样呢?接下来将switch的Case项再增加一项,看看会发生什么?

void funcA(int a,int b){
    int c = 0;
    switch(a){
        case 1:
            c += 1;
            break;
        case 2:
            c += 2;
            break;
        case 3:
            c += 3;
            break;
        case 4:
            c += 4;
            break;
        default:
            c = b;
            break;
    }
}
//其汇编如下:
Demo`funcA:
    0x104676190 <+0>:   sub    sp, sp, #0x20             ; =0x20 
    0x104676194 <+4>:   str    w0, [sp, #0x1c]           ; =0x1
    0x104676198 <+8>:   str    w1, [sp, #0x18]           ; =0x2
    0x10467619c <+12>:  str    wzr, [sp, #0x14]          ; =0x0
->  0x1046761a0 <+16>:  ldr    w8, [sp, #0x1c]           ; =0x1
    0x1046761a4 <+20>:  subs   w8, w8, #0x1              ; =0x1 
    0x1046761a8 <+24>:  mov    x9, x8
    0x1046761ac <+28>:  ubfx   x9, x9, #0, #32
    0x1046761b0 <+32>:  cmp    x9, #0x3                  ; =0x3 
    0x1046761b4 <+36>:  str    x9, [sp, #0x8]
    0x1046761b8 <+40>:  b.hi   0x104676214               ; <+132> at main.m:65:17
    0x1046761bc <+44>:  adrp   x8, 0
    0x1046761c0 <+48>:  add    x8, x8, #0x224            ; =0x224 
    0x1046761c4 <+52>:  ldr    x11, [sp, #0x8]
    0x1046761c8 <+56>:  ldrsw  x10, [x8, x11, lsl #2]
    0x1046761cc <+60>:  add    x9, x8, x10
    0x1046761d0 <+64>:  br     x9
    0x1046761d4 <+68>:  ldr    w8, [sp, #0x14]
    0x1046761d8 <+72>:  add    w8, w8, #0x1              ; =0x1 
    0x1046761dc <+76>:  str    w8, [sp, #0x14]
    0x1046761e0 <+80>:  b      0x10467621c               ; <+140> at main.m:68:1
    0x1046761e4 <+84>:  ldr    w8, [sp, #0x14]
    0x1046761e8 <+88>:  add    w8, w8, #0x2              ; =0x2 
    0x1046761ec <+92>:  str    w8, [sp, #0x14]
    0x1046761f0 <+96>:  b      0x10467621c               ; <+140> at main.m:68:1
    0x1046761f4 <+100>: ldr    w8, [sp, #0x14]
    0x1046761f8 <+104>: add    w8, w8, #0x3              ; =0x3 
    0x1046761fc <+108>: str    w8, [sp, #0x14]
    0x104676200 <+112>: b      0x10467621c               ; <+140> at main.m:68:1
    0x104676204 <+116>: ldr    w8, [sp, #0x14]
    0x104676208 <+120>: add    w8, w8, #0x4              ; =0x4 
    0x10467620c <+124>: str    w8, [sp, #0x14]
    0x104676210 <+128>: b      0x10467621c               ; <+140> at main.m:68:1
    0x104676214 <+132>: ldr    w8, [sp, #0x18]
    0x104676218 <+136>: str    w8, [sp, #0x14]
    0x10467621c <+140>: add    sp, sp, #0x20             ; =0x20 
    0x104676220 <+144>: ret  

从汇编代码中看到,当Case项大于3个时,switch底层汇编跟if语句完全不一样。
这就是switch底层优化,如果判断条件大于3个的时候,就会创建一个表,表里面存储了每一个Case对应的要执行的代码的地址。此时switch的Case判断变成查表了。

这段代码有两个重点:
【第一部分】判断是否为default项

0x1046761a0 <+16>:  ldr    w8, [sp, #0x1c]           ; =0x1
0x1046761a4 <+20>:  subs   w8, w8, #0x1              ; =0x1 
0x1046761a8 <+24>:  mov    x9, x8
0x1046761ac <+28>:  ubfx   x9, x9, #0, #32
0x1046761b0 <+32>:  cmp    x9, #0x3                  ; =0x3 
0x1046761b4 <+36>:  str    x9, [sp, #0x8]
0x1046761b8 <+40>:  b.hi   0x104676214               ; <+132> at main.m:65:17

这部分的工作主要是判断当前switch(a)的值是否是default项

当Case标号之间的差值较小时,编译器会将Case编号换算成一个[最小值 - 最大值]的区间。当switch接收到标号不处于该区间范围内,则认定为default项。

在本例中,Case标号是从1-4的连续值。因此判断过程可理解成:

  • Case标号的值为【1-4】的连接值,可以将其转换为【0-3】的区间范围。
  • 若a-1属于【0-3】的区间范围,则表示a等于某一个Case标号。
  • 若a-1不属于【0-3】的区间范围,则表示a为default项。

注意:这里需要补充一个指令ubfx

指令格式:ubfx Xd, Xn, #lsb, #width
意义:从Xn寄存器的第lsb位,提取宽度为width位的数据到Xd寄存器。剩余高位用 0 填充

//表示从X9寄存器的第0位开始,读取宽度为32位的数据,到X9寄存器,其中X9的高32位用0填充
ubfx   x9, x9, #0, #32

【第二部分】Case匹配

0x1046761bc <+44>:  adrp   x8, 0
0x1046761c0 <+48>:  add    x8, x8, #0x224            ; =0x224 
0x1046761c4 <+52>:  ldr    x11, [sp, #0x8]
0x1046761c8 <+56>:  ldrsw  x10, [x8, x11, lsl #2]
0x1046761cc <+60>:  add    x9, x8, x10
0x1046761d0 <+64>:  br     x9

在理解这部分内容之前,需要先了解两个知识点。

  • 知识点1
    当Case的标号差值较小时,编译器在编译的时候,会在栈空间中分配一块连续的空间,用于存储各Case标号将要执行的地址当前栈空间起始地址差值

    为什么存储的是差值而不直接存储地址,那是因为ASLR(地址随机化)机制,所以编译器在编译的时候并不知道程序执行的真实地址。

    如上述汇编中,存储差值栈空间起始地址为:0x104676224

    0x1046761bc <+44>:  adrp   x8, 0
    0x1046761c0 <+48>:  add    x8, x8, #0x224            ; =0x224 
    

    该栈空间内存在的内容为各Case标号将要执行的地址当前栈空间起始地址差值。每个差值4个字节的负数(因为switch所在函数栈空间地址低于存储差值的栈空间地址)。

    (lldb) x/4g 0x0000000104676224
    0x104676224: 0xffffffc0ffffffb0 0xffffffe0ffffffd0
    0x104676234: 0xd2800041d2800020 0xd65f03c0eb01001f
    
  • 知识点2
    LDR指令的格式:
    LDR{条件} 目的寄存器 <存储器地址>

    LDRSW指令:这个指令也是从内存中加载数据到寄存器。只是添加了两个条件项。“S”表示需要符号扩展;“W”: word表示16字节

    0x1046761c8 <+56>:  ldrsw  x10, [x8, x11, lsl #2]
    1. 将x11的值,进行左移两位,假设X11=0x03(0b0011),左移两位的结果为0x0C(0b1100)。
    2. 上面语句可以转换成:ldrsw  x10, [x8, 0x0c]。这个就很好理解了,取出x8偏移0x0c位置的值,并将其带符号扩展成16字节后赋值给x10。
    

    接下来通过一个简单的示例来看一下该指令的执行结果

    假设x11的值为3,x8里面存储的数据如下:
    (lldb) x/4g 0x0000000104676224
    0x104676224: 0xffffffc0ffffffb0 0xffffffe0ffffffd0
    0x104676234: 0xd2800041d2800020 0xd65f03c0eb01001f
    
    ldrsw  x10, [x8, x11, lsl #2]执行结果:
    1. x11进行左移2位,0x03(0b0011) --> 0x0c(0b1100)
    2. 读取x8偏移0x0c的值。即0xffffffe
    3. 将结果进行带符号扩展,并赋值给x10。因此x10 = 0xffffffffffffffe0
    

有了这两个知识点,就能很容易理解【第二部分】内容了。
* 第一步:找到存储差值栈空间地址
* 第二步:根据当前switch的值在栈空间中找到差值
* 第三步:将栈空间地址差值相加得到Case语句块的地址,并跳转至该地址。

【第三部分】执行Case语句块,执行完成后跳出switch语句。

0x1046761d4 <+68>:  ldr    w8, [sp, #0x14]
0x1046761d8 <+72>:  add    w8, w8, #0x1              ; =0x1 
0x1046761dc <+76>:  str    w8, [sp, #0x14]
0x1046761e0 <+80>:  b      0x10467621c               ; <+140> at main.m:68:1

这部分内容比较好理解了,这里就不再详细说明了。

情况3:Case大于4个,标号差值较大的情况

假设当前switch语句中,Case的个数大于4个,但各Case的差值较大,来看一下其汇编。

Demo`funcA:
    0x102d42194 <+0>:   sub    sp, sp, #0x10             ; =0x10 
    0x102d42198 <+4>:   str    w0, [sp, #0xc]
    0x102d4219c <+8>:   str    w1, [sp, #0x8]
->  0x102d421a0 <+12>:  ldr    w8, [sp, #0xc]
    0x102d421a4 <+16>:  cmp    w8, #0x2                  ; =0x2 
    0x102d421a8 <+20>:  str    w8, [sp, #0x4]
    0x102d421ac <+24>:  b.eq   0x102d421f4               ; <+96> at main.m:55:15
    0x102d421b0 <+28>:  b      0x102d421b4               ; <+32> at main.m
    0x102d421b4 <+32>:  ldr    w8, [sp, #0x4]
    0x102d421b8 <+36>:  cmp    w8, #0x1e                 ; =0x1e 
    0x102d421bc <+40>:  b.eq   0x102d42204               ; <+112> at main.m:58:15
    0x102d421c0 <+44>:  b      0x102d421c4               ; <+48> at main.m
    0x102d421c4 <+48>:  ldr    w8, [sp, #0x4]
    0x102d421c8 <+52>:  cmp    w8, #0x64                 ; =0x64 
    0x102d421cc <+56>:  b.eq   0x102d421e4               ; <+80> at main.m:52:15
    0x102d421d0 <+60>:  b      0x102d421d4               ; <+64> at main.m
    0x102d421d4 <+64>:  ldr    w8, [sp, #0x4]
    0x102d421d8 <+68>:  cmp    w8, #0xc8                 ; =0xc8 
    0x102d421dc <+72>:  b.eq   0x102d42214               ; <+128> at main.m:61:15
    0x102d421e0 <+76>:  b      0x102d42224               ; <+144> at main.m
    0x102d421e4 <+80>:  ldr    w8, [sp, #0x8]
    0x102d421e8 <+84>:  add    w8, w8, #0x1              ; =0x1 
    0x102d421ec <+88>:  str    w8, [sp, #0x8]
    0x102d421f0 <+92>:  b      0x102d4222c               ; <+152> at main.m:67:1
    0x102d421f4 <+96>:  ldr    w8, [sp, #0x8]
    0x102d421f8 <+100>: add    w8, w8, #0x2              ; =0x2 
    0x102d421fc <+104>: str    w8, [sp, #0x8]
    0x102d42200 <+108>: b      0x102d4222c               ; <+152> at main.m:67:1
    0x102d42204 <+112>: ldr    w8, [sp, #0x8]
    0x102d42208 <+116>: add    w8, w8, #0x3              ; =0x3 
    0x102d4220c <+120>: str    w8, [sp, #0x8]
    0x102d42210 <+124>: b      0x102d4222c               ; <+152> at main.m:67:1
    0x102d42214 <+128>: ldr    w8, [sp, #0x8]
    0x102d42218 <+132>: add    w8, w8, #0x4              ; =0x4 
    0x102d4221c <+136>: str    w8, [sp, #0x8]
    0x102d42220 <+140>: b      0x102d4222c               ; <+152> at main.m:67:1
    0x102d42224 <+144>: mov    w8, #0x5
    0x102d42228 <+148>: str    w8, [sp, #0x8]
    0x102d4222c <+152>: add    sp, sp, #0x10             ; =0x10 
    0x102d42230 <+156>: ret    

从汇编来看,当Case标号差值较大时,其汇编与if else相似。

总结

  • 当Case个数小于4个时,switch汇编if else汇编相似。
  • 当Case个数大于4个,但各标号之间的差值较小时,switch语句在编译时,会拉取一段栈空间,空间大小为:(Case标号最大值 - Case标号最小值+1)*4,这段空间用于存储各Case对应的语句块的地址当前栈空间起始地址差值,是一个4字节的数据。注意:如果Case标号不连续,则存储default的地址当前栈空间起始地址差值
  • 当Case个数大于4个,但各标号之间的差值较大时,其汇编也与if else类似。

你可能感兴趣的:(04 - ADRP指令&cmp指令&switch汇编)