想要深入地理解語言的運行機理,閱讀匯編代碼是很有幫助的。
前奏:我們這里用的匯編代碼格式是AT&T的,這個微軟的intel格式不一樣。
AT&T格式是GCC,OBJDUMP等一些其他我們在linux環境下常用工具的默認格式。
今天就一起再來看看switch語句吧。
關鍵詞:跳轉,跳轉表
先來一個最簡單的例子:
1 int switch_eg(int x, int n) 2 { 3 int result = x; 4 5 switch (n) { 6 case 100: 7 result += 10; 8 break; 9 case 102: 10 result -= 10; 11 break; 12 default: 13 result = 0; 14 } 15 16 return result; 17 }
看一下其匯編代碼:我會逐條注釋。
命令是gcc -O1 -S test2.c
1 .file "test2.c" 2 .text 3 .globl switch_eg 4 .type switch_eg, @function 5 switch_eg: 6 .LFB0: 7 .cfi_startproc 8 movl 4(%esp), %ecx //x在esp+4的位置,存入寄存器ecx 9 movl 8(%esp), %edx //n在esp+8的位置,存入寄存器edx 10 leal 10(%ecx), %eax //將ecx的值+10存入eax,也就是x+10 11 cmpl $100, %edx //將n和100比較 12 je .L2 //n==100,跳到L2 13 subl $10, %ecx //n!=100,x=x-10 14 cmpl $102, %edx //n和102比較 15 movl $0, %eax //eax=0 16 cmove %ecx, %eax //如果n==102,eax=ecx 17 .L2: 18 rep //返回,result存在eax 19 ret 20 .cfi_endproc 21 .LFE0: 22 .size switch_eg, .-switch_eg 23 .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" 24 .section .note.GNU-stack,"",@progbits
好了,對着注釋看,是不是很簡單呢。由此我們知道,switch語句實際上也是一種條件語句,而條件語句的核心是跳轉。聰明的你應該還會想到跳轉的標簽個數應該是和case語句的分支個數成正比的。
可是,當case語句分支很多時,豈不是各種jmp?編譯器很聰明的使用了一種叫跳轉表的方法來解決這個問題。
其實也簡單,跳轉表的思想就是將要跳轉的代碼的地址存入一個數組中,然后根據不同的條件跳轉到對應的地址處,就像訪問數組一樣。
空說太枯燥了,還是看個例子吧。(例子來源:深入理解計算機系統3.6.7)
C:
1 int switch_eg(int x, int n) { 2 int result = x; 3 switch (n) { 4 case 100: 5 result *= 13; 6 break; 7 8 case 102: 9 result += 10; 10 /* fall throuth */ 11 12 case 103: 13 result += 11; 14 break; 15 16 case 104: 17 case 106: 18 result *= result; 19 break; 20 21 default: 22 result = 0; 23 } 24 25 return result; 26 }
同樣的看一下對應的匯編。我省略了一些無關的代碼。
1 movl 4(%esp), %eax 2 movl 8(%esp), %edx 3 subl $100, %edx 4 cmpl $6, %edx 5 ja .L8 6 jmp *.L7(,%edx,4) 7 .section .rodata 8 .align 4 9 .align 4 10 .L7: 11 .long .L3 12 .long .L8 13 .long .L4 14 .long .L5 15 .long .L6 16 .long .L8 17 .long .L6 18 .text 19 .L3: 20 leal (%eax,%eax,2), %edx 21 leal (%eax,%edx,4), %eax 22 ret 23 .L4: 24 addl $10, %eax 25 .L5: 26 addl $11, %eax 27 ret 28 .L6: 29 imull %eax, %eax 30 ret 31 .L8: 32 movl $0, %eax 33 ret
解釋一下關鍵點:
首先生成了一張跳轉表,以L7為基准,4自己為對齊單位,加上偏移就能跳轉到相應的標簽。
比如,L7+0就是跳到L3處,L7+4就是跳轉到L8處,依次類推。
7 .section .rodata 8 .align 4 9 .align 4
10 .L7: 11 .long .L3 12 .long .L8 13 .long .L4 14 .long .L5 15 .long .L6 16 .long .L8 17 .long .L6
第6行: jmp *.L7(,%edx,4)
表示 goto *jt[index],舉個例子,假設現在n是102,edx里面是2(102-100),查表得L7+2*4處,即跳到L4處。
23 .L4: 24 addl $10, %eax
將eax的值+10,這和C是對應的。
8 case 102: 9 result += 10;
注意到L4后面沒有ret了,這就是我們上篇所說的fall through規則。不清楚可以看一下上篇的例子C語言拾遺(四):分析switch語句機制---上篇。
好了,其他的分支,各位可以自己用其他例子驗證一下,看是不是跟C語言代碼邏輯是一樣的,歡迎討論。
小結:
swith語句的本質是條件語句,條件語句的本質是跳轉。
當case分支多了的時候(一般大於四個時),編譯器巧妙地通過跳轉表來訪問代碼位置。
---End---