本文主要講解Switch的匯編代碼
Switch
1、假設(shè)
switch語句的分支比較少時(shí)(例如3,少于4的時(shí)候沒有意義),沒有必要使用次結(jié)構(gòu),相當(dāng)于if-else2、各個(gè)
分支常量的差值較大時(shí),編譯器會(huì)在效率還是內(nèi)存進(jìn)行取舍,這時(shí)編譯器還是會(huì)編譯成類似于if-else的結(jié)構(gòu)3、在
分支比較多的時(shí)候,在編譯的時(shí)候會(huì)生成一個(gè)表,不同的case通過跳轉(zhuǎn)表的不同地址,每個(gè)地址占四個(gè)字節(jié)。
案例分析
1、當(dāng)case有3個(gè)時(shí)
void func(int a){
switch (a) {
case 1:
printf("打坐");
break;
case 2:
printf("加紅");
break;
case 3:
printf("加藍(lán)");
break;
default:
printf("啥也不干");
break;
}
}
int main(int argc, char * argv[]) {
func(1);
- 斷點(diǎn)調(diào)試func
image
其中3個(gè)case的匯編是以if-else的形式
2、如果是4個(gè)case呢?
void func(int a){
switch (a) {
case 1:
printf("打坐");
break;
case 2:
printf("加紅");
break;
case 3:
printf("加藍(lán)");
break;
case 4:
printf("打怪");
break;
default:
printf("啥也不干");
break;
}
}
int main(int argc, char * argv[]) {
func(1);
}
-
此時(shí)
w8就是傳入的參數(shù),即w8=4
image -
subs w8, w8, #0x1:subs會(huì)影響目標(biāo)寄存器,即影響w8(注:但不執(zhí)行匯編命令:ni)
image
-
ubfx x9, x9, #0, #32:將x9高32位清零,下面通過修改x9寄存器的值來驗(yàn)證:確實(shí)是將高32位清零(從x9第0位開始,保留低32位,高位用0補(bǔ)齊)
image
下面是實(shí)際操作后的結(jié)果
image -
執(zhí)行
cmp x9 #0x4~ldr x11,[sp],在這里是x9和4比較,判斷是否等于。此時(shí)的結(jié)果是不等于則繼續(xù)往下執(zhí)行
image -
ldrsw x10, [x8, x11, lsl #2](先運(yùn)算后面,再運(yùn)算前面):將x8的值加上x11(需要先將x11左移2位,即0x3左移2位,為1100,為12)作為地址,然后將地址的值給x10,此時(shí)x10的值就是-24 ????(即0xffffffffffffffe8)
image
執(zhí)行ldrsw x10, [x8, x11, lsl #2]后的x10
image
從x8 = 0x0000000102e22828與最后0x102e22824對(duì)比,可以看出,x8的地址是func執(zhí)行完后下一句代碼的地址
image -
add x9, x8, x10:x8的地址加上x10偏移(由于x10為負(fù)數(shù),所以是x8-x10),給到x9.即x9的值為0x102e22828-0x18(24的十六進(jìn)制)=0x102e22810,即到下面這句,這里就是走到default
image -
執(zhí)行到
ldp x29, x30, [sp, #0x10],輸出啥也不干(lldb)
image
3、修改案例
void func(int a){
switch (a) {
case 5:
printf("打坐");
break;
case 6:
printf("加紅");
break;
case 7:
printf("加藍(lán)");
break;
case 8:
printf("打怪");
break;
default:
printf("啥也不干");
break;
}
}
int main(int argc, char * argv[]) {
func(4);
}
其匯編如下,發(fā)現(xiàn)subs減的是5

-
匯編代碼分析如下
image
4、修改2:將第二個(gè)case修改為2
void func(int a){
switch (a) {
case 5:
printf("打坐");
break;
case 2:
printf("加紅");
break;
case 7:
printf("加藍(lán)");
break;
case 8:
printf("打怪");
break;
default:
printf("啥也不干");
break;
}
}
匯編如下,從這里可以發(fā)現(xiàn),subs后減的是最小的case

疑問1:其中的
cmp x9,#0x6,這里的0x6又是怎么來的呢?其實(shí)是最小case與最大case(即8-2)的差值,疑問2:為什么要這么比較呢?主要是為了確認(rèn)傳入的參數(shù)
是否在[最小case,最大case]這個(gè)區(qū)間內(nèi),如果不在,則走default-
疑問3:為什么是無符號(hào)?因?yàn)槿绻麄魅氡茸钚ase小的值,例如傳入的是1,得到的是一個(gè)負(fù)數(shù),
如果是
有符號(hào)數(shù)運(yùn)算,一定比區(qū)間值小如果是
無符號(hào)數(shù)運(yùn)算,是一個(gè)非常大的數(shù),一定超過了范圍,直接走到default
分析
-
代碼中有一張表,表中有7個(gè)字節(jié),都是負(fù)數(shù),為什么是7個(gè)?因?yàn)?-2=6,加上default,所以是7個(gè)。
image
以下是switch表中的7個(gè)字節(jié)
imageswitch分支的代碼是連續(xù)的
結(jié)論:表中的字節(jié)數(shù),取決于
最大case-最小case+1(1表示default)目的:為什么這么創(chuàng)建?
用空間換時(shí)間,為了讓代碼效率更高ldrsw為什么是左移2位?因?yàn)橐粋€(gè)字節(jié)是4位,便于查找下一個(gè)case的值(x11 是 參數(shù)-最小case的值)
switch總結(jié)
1、先將參數(shù)減去最小case
2、先判斷是否是default,如果不是,則在范圍之內(nèi),可以通過表直接拿到地址,反之則default
-
疑問:表中為什么不直接存地址,而是存差值?
- 1)地址太長了
- 2)!!地址在運(yùn)行時(shí)期才會(huì)知道虛擬地址(即ASLR),如果存的是偏移地址,使用時(shí)需要加上ASLR
如果case很大呢?
- 只要是連續(xù)的就行,因?yàn)閰R編會(huì)減去最小case
- 如果case之間相差太大了,編譯器會(huì)進(jìn)行優(yōu)化,變成if-else
void func(int a){
switch (a) {
case 1:
printf("打坐");
break;
case 400:
printf("加紅");
break;
case 800:
printf("加藍(lán)");
break;
case 8:
printf("打怪");
break;
default:
printf("啥也不干");
break;
}
}
int main(int argc, char * argv[]) {
func(4);
}

總結(jié)
1、假設(shè)
switch語句的分支比較少時(shí)(例如3,少于4的時(shí)候沒有意義),沒有必要使用次結(jié)構(gòu),相當(dāng)于if-else2、各個(gè)分支常量的
差值較大時(shí),編譯器會(huì)在效率還是內(nèi)存進(jìn)行取舍,這時(shí)編譯器還是會(huì)編譯成類似于if-else的結(jié)構(gòu)3、在
分支比較多的時(shí)候,在編譯的時(shí)候會(huì)生成一個(gè)表(跳轉(zhuǎn)表每個(gè)地址四個(gè)字節(jié))。













