每年都有應屆畢業生來到公司,每年都要對新同事進行**方面的培訓,比如編碼規範就是其中之一。編碼規範初聽起來比較新鮮,但是培訓時間長了,顯然有些乏味。今年我打算改變策略,讓新同事結合已有規範文件和專案**,自己先挖掘一遍,然後大家通過坐下來討論的互動方式來加深對規範的理解,每次討論時間限制在1 hour以內,不給大家打瞌睡的機會^_^。
上週和新同事一起討論表示式和語句,說到了switch和if,談到了他們的用途和區別。大家都清楚switch語句被稱為多分支語句,當**中即將出現3個及3個以上分支時,推薦用switch,這樣**可讀性好,清晰,格式工整;但是同樣switch也是有侷限的,就是switch(xx)中的xx必須是整型變數;如果你的條件判斷是字串比較,就無法直接使用switch了。switch的這一侷限實際上是有原因的,為什麼呢?在於其效能優化。那switch語句在底層到底是如何實現的呢?和if語句相比,switch除了美觀之外,優勢又在**呢?我們唯有到彙編層去看個究竟了。
我們先來看看if多分支的情況://windows xp + gcc v3.4.2 (mingw-special)
//testif.c
int test_if_performance(int i) else if (rv == 11) else if (rv == 12) else if (rv == 13||rv == 14 || rv == 15) else
return rv; }
我們通過-s選項得到test_if_performance的彙編**,我們加上了-o2的優化選項:
//gcc -s o2 testif.c
//testif.s
... ...
_test_if_performance:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %edx
cmpl $10, %edx
je l11
cmpl $11, %edx
je l12
cmpl $12, %edx
je l13
leal -13(%edx), %eax
cmpl $2, %eax
ja l3
addl $105, %edx
l3:popl %ebp
movl %edx, %eax
ret.p2align 4,,7
l11:
popl %ebp
movl $110, %edx
movl %edx, %eax
ret.p2align 4,,7
l12:
popl %ebp
movl $112, %edx
movl %edx, %eax
ret.p2align 4,,7
l13:
popl %ebp
movl $114, %edx
movl %edx, %eax
ret從這段彙編碼來看,if語句是逐個判斷下來的,如果i = 19的話,程式需要從頭判斷到尾,"乙個都不能少"^_^。那麼擁有同樣語義功能的switch**又是如何實現的呢?我們繼續看下去。
// testswitch.c 這個檔案實現的是和上述testif.c同樣的功能
int test_switch_performance(int i)
return rv; }
我們同樣用-o2來得到switch的彙編**:
//gcc -s o2 testswitch.c
//testswitch.s
... ...
_test_switch_performance:
pushl %ebp
movl %esp, %ebp
movl 8(%ebp), %ecx
leal -10(%ecx), %edx
movl %ecx, %eax
cmpl $5, %edx
ja l2
jmp *l10(,%edx,4)
.section .rdata,"dr"
.align 4
l10:
.long l3
.long l4
.long l5
.long l8
.long l8
.long l8
.text
.p2align 4,,7
l8:leal 105(%ecx), %eax
.p2align 4,,15
l2:popl %ebp
ret.p2align 4,,7
l3:popl %ebp
leal 100(%ecx), %eax
ret.p2align 4,,7
l4:popl %ebp
leal 101(%ecx), %eax
ret.p2align 4,,7
l5:popl %ebp
leal 102(%ecx), %eax
ret看完彙編碼,第一感覺:cmpl少了許多,乙個唯讀資料段中的l10的標籤映入眼簾,以l10標籤為起始的記憶體中依次儲存了l3、l4、l5和三個l8的位址,看起來就像是乙個位址陣列,或者是乙個位址表,訪問這個陣列中的元素實際上就是呼叫每個元素對應位址中的一段**。我們繼續往前看,來證實一下這個想法。**不多,比對著彙編指令手冊讀起來也不甚難。
pushl %ebp
movl %esp, %ebp // 將棧幀位址存在%ebp中
movl 8(%ebp), %ecx // 將rv值儲存到%ecx中
leal -10(%ecx), %edx // 將rv值-10之後的值,作為位址偏移量存放到%edx
movl %ecx, %eax // 將%ecx中的rv值儲存到%eax中
cmpl $5, %edx // 比較5 vs. (rv - 10),顯然5是編譯器經過**掃瞄後,算出的乙個最大偏移值
ja l2 // jump if above ,如果5 > %edx中的值,則跳到l2繼續執行
jmp *l10(,%edx,4) // 如果5 <= %edx中的值,則jmp *l10(,%edx,4)
解析一下jmp *l10(,%edx,4),按照書中所說,*l10(,%edx,4)應該對應乙個叫indexed memory mode的模式,格式一般是base_address(offset_address, index, size),含義就是base_address + offset_address + index * size;這樣似乎就一目了然了。我們拿i = 12為例,經過前面的計算,%edx中儲存的是2,l10(,%edx,4)相當於l10 + 0 + 2 * 4,也就是起始位址=l10 + 8的那個記憶體區域,恰好是l5的起始位址,jmp *l10(,%edx,4),直接將**執行routine轉到l5了:
l5:popl %ebp
leal 102(%ecx), %eax
ret顯然這和前面的猜測是一致的,switch並沒有使用效能低下的逐個cmpl的方式,而是形成了乙個跳轉表(以l10為首位址的位址陣列),並將傳入switch的那個整型值經過已經的運算後作為offset值,通過乙個jmp直接轉到目的**區,這樣無論switch有多少個分支,實際上都只是做了一次cmpl,效能照比多if有很大提公升。
語句 switch語句
switch語句的特點如下 1 switch x 被選擇的內容 即x 只能是byte,short,int,char這四種型別 2 備選答案並沒有指定的順序,但是執行肯定是從第乙個case開始的,如果其中有匹配的case,執行完,通過該case的break就結束了switch。如果沒有匹配的case,...
C 效能剖析教程之switch語句
前言 幾乎每本面向初學者的c語言或c 書籍在前面兩章都會提到分支控制語句if else和switch case,在某些情況下這兩種分支控制語句可以互相替換,但卻很少有人去深究在if else和switch case語句的背後到底有什麼異同?應該選擇哪乙個語句才能使得效率最高?要回答這些問題,只能走到...
if語句和switch語句
利用if else構建分支結構if 表示式 語句1 else else部分是可選的 語句2 當表示式為真的時候,執行語句1,當表示式為假的時候,並且有else語句就執行語句2。if語句巢狀的時候,每乙個else要與最近的且沒有else語句的if進行匹配。例 if i 0 if a b else 例 ...