C語言拾遺(五):分析switch語句機制---下篇


想要深入地理解語言的運行機理,閱讀匯編代碼是很有幫助的。

前奏:我們這里用的匯編代碼格式是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---


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM