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
以上代码中,在获取字符串常量和全局变量时,出现了一个新的指令ADRP
。ADRP
:这是一条小范围的地址读取指令
,它将基于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
这两句指令的意思:
- 根据
页面号
,将x8寄存器指向页面号对应的内存页。 - 再将进行
页内偏移值
,找到目标虚拟地址。
根据以上解释,我们通过手动计算x8的地址。
- 计算当前pc寄存器指向地址所处的内存页,由于内存页大小为
4K(0x1000)
,因此将其低12位置为0
即为结果,结果为:0x104ece000,该页的寻址范围为:0x104ece000 - 0x104ecefff
- 偏移至1号内存页:x8 = 0x104ece000 + 1*4096 = 0x104ecf000
- 页内偏移: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的字符串常量
接下来,看一下全局变量g
的ADRP
指令
//获取全局变量g的地址
0x104ece22c <+36>: adrp x8, 3
0x104ece230 <+40>: add x8, x8, #0x5b0 ; =0x5b0
- 计算当前pc寄存器指向地址所处的内存页,由于内存页大小为
4K(0x1000)
,因此将其低12位置为0即为结果,结果为:0x104ece000,该页的寻址范围为:0x104ece000 - 0x104ecefff
- 偏移至3号内存页:x8 = 0x104ece000 + 3*4096 = 0x104ed1000
- 页内偏移: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(地址随机化)
机制,所以编译器在编译的时候并不知道程序执行的真实地址。如上述汇编中,
存储差值
的栈空间起始地址
为:0x1046762240x1046761bc <+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
类似。