前言
幾乎每本面向初學者的c語言或c++書籍在前面兩章都會提到分支控制語句if……else和switch……case,在某些情況下這兩種分支控制語句可以互相替換,但卻很少有人去深究在if……else和switch……case語句的背後到底有什麼異同?應該選擇哪乙個語句才能使得效率最高?要回答這些問題,只能走到switch語句的背後,看看這些語句到底是怎麼實現的。
基本格式
switch語句的基本格式如下:
switch (表示式)
其中:if語句與switch語句
相信學過c/c++的同學對這兩個語句的異同早就瞭如指掌,if語句作為條件判斷,滿足條件進入if語句塊,不滿足條件則進入else語句塊,而且if和else語句塊又可以繼續巢狀if語句。switch則是通過判斷乙個整型表示式的值來決定進入到哪乙個case語句中,如果所有case條件都不滿足則進入到default語句塊。
//簡單的if語句
if (a == 1)
i = 1;
else if (a == 2)
i = 2;
else
i = 3;
//簡單的switch語句
switch (a)
編譯器如何實現switch語句?
現在編譯器已經足夠智慧型和強大,經過測試,g++實現switch語句的方式就至少有三種,編譯器會根據**的實際情況,權衡時間效率和空間效率,去選擇乙個對當前**而言綜合效率最高的一種。
編譯器實現switch語句的三種方式:
後面我們將就這三種實現方法適用的**場景進行測試和分析。
1. 逐條件判斷法
逐條件判斷法其實就是和if……else語句的彙編實現相同,編譯器把switch語句中各個case條件逐個進行判斷,直到找到正確的case語句塊。這種方法適用於switch語句中case條件很少的情況,即使逐個條件判斷也不會導致大量時間和空間的浪費,比如下面這段**:
#include
int test_switch()
return i;
}該**對應的彙編**如下:
movl -4(%rbp), %eax
cmpl $1, %eax
je .l3
cmpl $2, %eax
je .l4
testl %eax, %eax
jne .l8
movl $0, -8(%rbp)
jmp .l6
.l3:
movl $1, -8(%rbp)
jmp .l6
.l4:
movl $2, -8(%rbp)
jmp .l6
.l8:
movl $3, -8(%rbp)
nopeax暫存器儲存的是判斷條件值(對應於c++**中的a值),首先判斷a是否等於1,如果等於1則跳轉到.l3執行a==1對應的**段,然後判斷a是否等於2,如果等於2則跳轉到.l4執行a==2對應的**段……可能難理解的是第6行**testl %eax, %eax,其實這只是編譯器提高判斷乙個暫存器是否為0效率的乙個小技巧,如果eax不等於0則跳轉到.l8**段,執行default**段對應的**,如果eax等於0則執行a==0對應的**段。
由上面對編譯器生成彙編**的分析,我們可以發現:編譯器在這種情況下使用逐個條件判斷來實現switch語句。
2. 跳轉表實現法
在編譯器採用這種switch語句實現方式的時候,會在程式中生成乙個跳轉表,跳轉表存放各個case語句指令塊的位址,程式執行時,首先判斷switch條件的值,然後把該條件值作為跳轉表的偏移量去找到對應case語句的指令位址,然後執行。這種方法適用於case條件較多,但是case的值比較連續的情況,使用這種方法可以提高時間效率且不會顯著降低空間效率,比如下面這段**編譯器就會採用跳轉表這種實現方式:
#include
int test_switch()
return i;
}該**對應的彙編**如下:
movl -4(%rbp), %eax
movq .l4(,%rax,8), %rax
jmp *%rax
.l4:
.quad .l3
.quad .l5
.quad .l6
.quad .l7
.quad .l8
.quad .l9
.quad .l10
.quad .l11
.quad .l12
.quad .l13
.text
.l3:
movl $0, -8(%rbp)
jmp .l14
.l5:
movl $1, -8(%rbp)
jmp .l14
#後面省略……
在x64架構中,eax暫存器是rax暫存器的低32位,此處我們可以認為兩者值相等,**第一行是把判斷條件(對應於c++**中的a值)複製到eax暫存器中,第二行**是把.l4段偏移rax暫存器值大小的位址賦值給zocvuaogjrax暫存器程式設計客棧,第三行**則是取出rax中存放的位址並且跳轉到該位址處。我們可以清楚的看到.l4**段就是編譯器為switch語句生成的存放於.text段的跳轉表,每種case均對應於跳轉表中乙個位址值,我們通過判斷條件的值即可計算出來其對應**段位址存放的位址相對於.l4的偏移,從而實現高效的跳轉。
3. 二分查詢法
如果case值較多且分布極其離散的話,如果採用逐條件判斷的話,www.cppcns.com時間效率會很低,如果採用跳轉表方法的話,跳轉表占用的空間就會很大,前兩種方法均會導致程式效率低。在這種情況下,編譯器就會採用二分查詢法實現switch語句,程式編譯時,編譯器先將所有case值排序後按照二分查詢順序寫入彙編**,在程式執行時則採二分查詢的方法在各個case值中查詢條件值,如果查詢到則執行對應的case語句,如果最終沒有查詢到則執行default語句。對於如下c++**編譯器就會採用這種二分查詢法實現switch語句:
#include
int test_switch()
return i;
}改**段對應的彙編**為:
movl -4(%rbp), %eax
cmpl $50, %eax
je .l3
cmpl $50, %eax
jg .l4
cmpl $4, %eax
je .l5
cmpl $10, %eax
je .l6
jmp .l2
.l4:
cmpl $200, %eax
je .l7
cmpl $500, %eax
je .l8
cmpl $100, %eax
je .l9
jmp .l2
**第二行條件值首先與50比較,為什麼是50而不是放在最前面的4?這是因為二分查詢首先查詢的是處於中間的值,所以這裡先與50進行比較,如果eax等於50,則執行case
50對應**,如果eax值大於50則跳轉到.l4**段,如果eax小於50則繼續跟4比較……直至找到條件值或者查詢完畢條件值不存在。可以看出二分查詢法在保持了較高的查詢效率的同時又節省了空間占用。
總結何時應該使用if……else語句,何時應該使用switch……case語句?
通過上面的分析我們可以得出結論,在可能條件比較少的時候使用if……else和switch……case所對應的彙編**是相同的,所以兩者在效能上是沒有區別的,使用哪一種取決於個人習慣。如果條件較多的話,顯而易見switch……case的效率更高,無論是跳轉表還是二分查詢都比if……else的順序查詢效率更高,所以在這種情況下盡量選用switch語句來實現分支語句。當然如果我們知道哪種條件出現的概率最高,我們可以將這個條件放在if判斷的第乙個,使順序查詢提前結束,這時使用if……else語句也可以達到較高的執行效率。
switch語句也有他本身的侷限性,即switch語句的值只能為整型,比如當我們需要對乙個double型資料進行判斷時,便無法使用switch語句,這時只能使用if……else語句來實現。
本文標題: c++效能剖析教程之switch語句
本文位址:
C語言初階 分支語句,if語句,switch語句
2switch語句 注意事項 if 表示式 2 二選一 if 表示式 else 3 多分支 多選一 if 表示式1 else if 表示式2 else 1.if語句是選擇分支語句,只要滿足乙個條件則其他語句就不執行了。2.if後 括號 判斷的是括號中表示式返回的結果。在c語言中,0為假,非0為真。3...
SUNWEN教程之 C 高階(七) C 教程
好了,言歸正傳.我要說的是c 中的結構 struct 注意,我在這裡說的結構不是指的c 的語言結構.這裡所說的是一種與類 class 相對的東西,下面我就與類相對比,來說一說這個struct.下面的這個例子講述了如何建立乙個具有屬性,方法和乙個欄位的結構.並講述如何使用他.000 structs s...
C 深度剖析教程38 類模板深度剖析
類模板可以定義任意多個不同的型別引數 類模板可以被特化 類模板的特化型別 看 include include using namespace std template typename t1,typename t2 class test template typename t1,typename t...