iOS逆向实战--005:判断 & 循环 & 选择
判断
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
:比较w8
、w9
寄存器,相当于比较参数1
和参数2
。cmp
指令相当于做减法,但只会影响状态寄存器,不会影响目标寄存器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
为参数1
,x9
为全局变量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
为参数2
,x9
为全局变量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 + 0xc
和sp + 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
循环,所以cmp
和b.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 + 0xc
和sp + 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
- 还原
x29
、x30
的值- 恢复栈平衡
- 返回
上述案例中,每个
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
- 开辟栈空间,现场保护
- 将
w0
在x29 - 0x4
地址入栈,然后再寻址写入w8
subs w8, w8, #0x1
:w8
为当前变量的值,减去#0x1
,写入w8
寄存器
w8
:viewDidLoad
方法中,调用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
填充
例如:x9
为0x0123456789abcdef
,此指令执行后,x9
为0x0000000089abcdef
cmp x9, #0x3
:将x9
的值和#0x3
进行比较
x9
:参数区间值
#0x3
:最大case
值减去最小case
值,得到case区间值
为3
- 将
x9
在sp
所在位置入栈b.hi 0x104105f48
:如果参数区间值 - case区间值 > 0
,执行标号跳转到第七段
,相当于进入default
的代码块。否则向下执行第二段
funcA
函数,第二段
。通过查表,获取跳转到指定case
或default
的代码标号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
:将x8
和x10
相加,写入x9
x8
:值为0x102269f60
。数据表首地址
x10
:值为0xffffffffffffffd8
,即:-40
,16进制
为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
- 还原
x29
、x30
的值- 恢复栈平衡
- 返回
上述案例中,
switch
语句生成的汇编代码逻辑【第一步】:判断参数值是否在
case
值范围之内
- 通过
参数值 - 最小case值
,得到参数区间值
- 通过
最大case值 - 最小case值
,得到case区间值
- 将
参数区间值
和case区间值
进行比较- 如果
参数区间值 > case区间值
,表示超出case
范围,直接跳转到default
代码标号。否则进入【第二步】
【第二步】:通过查表,获取跳转到指定
case
或default
的代码标号
- 获取
数据表首地址
- 计算
数据表首地址
加上参数区间值
偏移x
位后的地址- 通过计算后地址,从数据表中获取距代码标号的偏移值
- 通过
数据表首地址 + 16进制(偏移值)
,计算出指定case
或default
的代码标号- 虽然参数在
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
。因为编译器会在效率和内存中进行取舍