Switch樣式一
原C程式碼如下:
void __switch_1__(){
int value = 5;
switch (value) {
case 0:
printf("1");
break;
case 1:
printf("2");
break;
case 2:
printf("3");
break;
default:
printf("else");
break;
}
}
複製程式碼
彙編程式碼如下:
01-彙編-IF-SWITCH`__switch_1__:
0x100c96644 <+0>: sub sp, sp, #0x30 ; =0x30
0x100c96648 <+4>: stp x29, x30, [sp, #0x20]
0x100c9664c <+8>: add x29, sp, #0x20 ; =0x20
-> 0x100c96650 <+12>: mov w8, #0x5 ; w8 = 5
0x100c96654 <+16>: stur w8, [x29, #-0x4]
0x100c96658 <+20>: ldur w8, [x29, #-0x4] ; value = w8
0x100c9665c <+24>: mov x9, x8
0x100c96660 <+28>: stur w9, [x29, #-0x8]
0x100c96664 <+32>: cbz w8, 0x100c96694 ; cbz 指令的意思是 如果w8==0,則跳轉到 0x100c96694,如果不等於0,則繼續執行程式碼
0x100c96668 <+36>: b 0x100c9666c ; <+40> at main.m:26
0x100c9666c <+40>: ldur w8, [x29, #-0x8]
0x100c96670 <+44>: subs w9, w8, #0x1 ; w9=w8-1
0x100c96674 <+48>: stur w9, [x29, #-0xc]
0x100c96678 <+52>: b.eq 0x100c966a8 ; 如果w9==0,則跳轉到 0x100c966a8
0x100c9667c <+56>: b 0x100c96680 ; <+60> at main.m:26
0x100c96680 <+60>: ldur w8, [x29, #-0x8]
0x100c96684 <+64>: subs w9, w8, #0x2 ; w9=w8-2
0x100c96688 <+68>: str w9, [sp, #0x10]
0x100c9668c <+72>: b.eq 0x100c966bc ; 如果w9==0,則跳轉到 0x100c966bc
0x100c96690 <+76>: b 0x100c966d0 ; 如果都不滿足即default,則跳轉到 0x100c966d0
0x100c96694 <+80>: adrp x0, 1
0x100c96698 <+84>: add x0, x0, #0xf29 ; =0xf29
0x100c9669c <+88>: bl 0x100c96c04 ; printf("1");
0x100c966a0 <+92>: str w0, [sp, #0xc]
0x100c966a4 <+96>: b 0x100c966e0 ; <+156> at main.m:40
0x100c966a8 <+100>: adrp x0, 1
0x100c966ac <+104>: add x0, x0, #0xf2b ; =0xf2b
0x100c966b0 <+108>: bl 0x100c96c04 ; printf("2");
0x100c966b4 <+112>: str w0, [sp, #0x8]
0x100c966b8 <+116>: b 0x100c966e0 ; <+156> at main.m:40
0x100c966bc <+120>: adrp x0, 1
0x100c966c0 <+124>: add x0, x0, #0xf2d ; =0xf2d
0x100c966c4 <+128>: bl 0x100c96c04 ; printf("3");
0x100c966c8 <+132>: str w0, [sp, #0x4]
0x100c966cc <+136>: b 0x100c966e0 ; <+156> at main.m:40
0x100c966d0 <+140>: adrp x0, 1
0x100c966d4 <+144>: add x0, x0, #0xf24 ; =0xf24
0x100c966d8 <+148>: bl 0x100c96c04 ; printf("else");
0x100c966dc <+152>: str w0, [sp]
0x100c966e0 <+156>: ldp x29, x30, [sp, #0x20]
0x100c966e4 <+160>: add sp, sp, #0x30 ; =0x30
0x100c966e8 <+164>: ret
複製程式碼
先科普一下兩個新指令:
- cbz :【cbz 暫存器,地址 】指令的意思是 如果暫存器的值==0,則跳轉到地址,如果不等於0,則繼續執行程式碼
- subs : subs其實就是sub指令,但是這個指令操作結束後,會影響到標誌暫存器
用一段最簡單的switch帶碼來介紹,這一段程式碼參照之前的IF語句,本質上一點區別都沒有,就是判斷跳轉,判斷跳轉
Switch樣式二
原C程式碼如下:
void __switch_2__(){
int value = 5;
switch (value) {
case 0:
printf("1");
break;
case 1:
printf("2");
break;
case 2:
printf("3");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
break;
default:
printf("else");
break;
}
}
複製程式碼
彙編程式碼如下:
01-彙編-IF-SWITCH`__switch_2__:
0x100be66c8 <+0>: sub sp, sp, #0x40 ; =0x40
0x100be66cc <+4>: stp x29, x30, [sp, #0x30]
0x100be66d0 <+8>: add x29, sp, #0x30 ; =0x30
0x100be66d4 <+12>: orr w8, wzr, #0x3
0x100be66d8 <+16>: stur w8, [x29, #-0x4]
0x100be66dc <+20>: ldur w8, [x29, #-0x4]
0x100be66e0 <+24>: mov x9, x8
0x100be66e4 <+28>: mov x8, x9
0x100be66e8 <+32>: subs w8, w8, #0x4 ; 如果 w8 > 0,則跳轉到 0x102fae774【加上前面的減法實際的意思就是如果w8>4的話就跳轉】
0x100be66ec <+36>: stur x9, [x29, #-0x10]
0x100be66f0 <+40>: stur w8, [x29, #-0x14]
0x100be66f4 <+44>: b.hi 0x100be6774 ; <+172> at main.m:62
0x100be66f8 <+48>: adrp x8, 0
0x100be66fc <+52>: add x8, x8, #0x790 ; x0 = 0x0000000100be6790
0x100be6700 <+56>: ldur x9, [x29, #-0x10]
0x100be6704 <+60>: ldrsw x10, [x8, x9, lsl #2]
-> 0x100be6708 <+64>: add x8, x10, x8
0x100be670c <+68>: br x8
0x100be6710 <+72>: adrp x0, 1
0x100be6714 <+76>: add x0, x0, #0xf29 ; =0xf29
0x100be6718 <+80>: bl 0x100be6c04 ; symbol stub for: printf
0x100be671c <+84>: str w0, [sp, #0x18]
0x100be6720 <+88>: b 0x100be6784 ; <+188> at main.m:65
0x100be6724 <+92>: adrp x0, 1
0x100be6728 <+96>: add x0, x0, #0xf2b ; =0xf2b
0x100be672c <+100>: bl 0x100be6c04 ; symbol stub for: printf
0x100be6730 <+104>: str w0, [sp, #0x14]
0x100be6734 <+108>: b 0x100be6784 ; <+188> at main.m:65
0x100be6738 <+112>: adrp x0, 1
0x100be673c <+116>: add x0, x0, #0xf2d ; =0xf2d
0x100be6740 <+120>: bl 0x100be6c04 ; symbol stub for: printf
0x100be6744 <+124>: str w0, [sp, #0x10]
0x100be6748 <+128>: b 0x100be6784 ; <+188> at main.m:65
0x100be674c <+132>: adrp x0, 1
0x100be6750 <+136>: add x0, x0, #0xf2d ; =0xf2d
0x100be6754 <+140>: bl 0x100be6c04 ; symbol stub for: printf
0x100be6758 <+144>: str w0, [sp, #0xc]
0x100be675c <+148>: b 0x100be6784 ; <+188> at main.m:65
0x100be6760 <+152>: adrp x0, 1
0x100be6764 <+156>: add x0, x0, #0xf2f ; =0xf2f
0x100be6768 <+160>: bl 0x100be6c04 ; symbol stub for: printf
0x100be676c <+164>: str w0, [sp, #0x8]
0x100be6770 <+168>: b 0x100be6784 ; <+188> at main.m:65
0x100be6774 <+172>: adrp x0, 1
0x100be6778 <+176>: add x0, x0, #0xf24 ; =0xf24
0x100be677c <+180>: bl 0x100be6c04 ; symbol stub for: printf
0x100be6780 <+184>: str w0, [sp, #0x4]
0x100be6784 <+188>: ldp x29, x30, [sp, #0x30]
0x100be6788 <+192>: add sp, sp, #0x40 ; =0x40
0x100be678c <+196>: ret
複製程式碼
其中的這一段彙編程式碼是核心
0x100be66f8 <+48>: adrp x8, 0
0x100be66fc <+52>: add x8, x8, #0x790 ; x8 = 0x0000000100be6790
0x100be6700 <+56>: ldur x9, [x29, #-0x10] ; x9 就是前面存進來的值 也就是3
0x100be6704 <+60>: ldrsw x10, [x8, x9, lsl #2] ; 取出[0x0000000100be6790 + 03 << 2],給x10
0x100be6708 <+64>: add x8, x10, x8 ; 計算出要調轉的位置
0x100be670c <+68>: br x8 ; 跳轉
複製程式碼
先科普一下adrp指令,之前在彙編(一) 中有大致介紹過 在本案例中 adrp x8, 0 :x8 = PC暫存器(0x100be66fc) 的 第十二位清零 + 0 << 12位 = 0x100be6000 接下來介紹當分支大於3的時候,編譯器做了什麼,首先編譯器會生成一個分支跳轉表,也就是我們0x100be66fc獲取到的值 我們在lldb環境下進行除錯:
(lldb) memory read 0x0000000100be6790
0x100be6790: 80 ff ff ff 94 ff ff ff a8 ff ff ff bc ff ff ff ................
0x100be67a0: d0 ff ff ff ff c3 00 d1 fd 7b 02 a9 fd 83 00 91 .........{......
複製程式碼
能發現他裡面幾個值分別是 0xFFFFFF80、0xFFFFFF94、0xFFFFFFBC、0xFFFFFFD0
ldrsw x10, [x8, x9, lsl #2] 這一句是取出列表的值
add x8, x10, x8 ;計算出跳轉的位置 0xffffffbc+0x0100be6790 = 0x0000000100be674c
複製程式碼
0x0000000100be674c這就是我們要跳轉的地址,這樣就不需要我們冗長的一步一步去判斷,大大提高了執行效率 所以,當我們實際開發的時候如果分支<=3,if與switch語句的執行效率是一致的,我們儘量在分支大於3的時候使用switch語句,當然有時候為了可讀性,這一點點效率也不用計較太多
Switch樣式三
void __switch_3__(){
int value = 5;
switch (value) {
case 100:
printf("1");
break;
case 4353:
printf("2");
break;
case 199:
printf("3");
break;
case 1:
printf("3");
break;
default:
printf("else");
break;
}
}
複製程式碼
當case的值,不規律的時候,他的彙編程式碼又會迴歸到 if - else if -else ..的彙編指令【這裡我就不再過多的分析彙編解釋】 所以我們寫case 必須是規律的且分支大於三的時候,最能保證我們程式碼的執行效率