switch case 語句在c語言里還是比較簡單的,但是被編譯出來之后,優化結果往往讓人很疑惑,完全看不懂,下面我們一次次的嘗試,看看編譯器到底把switch語句變成什么樣了。
① 先上個最簡單的:
switch ( argc ) { case 10: PRintf("case 10 ! /r/n"); break; case 11: printf("case 11 ! /r/n"); break; default: printf("default ! /r/n"); break; } getchar();丟進OD里,看下反匯編代碼:

第三行開始,取值到eax中
eax -= 10 ( 0xA )
if ( eax == 0 ) // 如果 eax - 10 == 0,直接可以得出結論 eax == 10
je 0x002C103E
else
{
eax--;
if ( eax == 0 ) // 剛上面 eax - 10 了,這里又減 1,一起就是 如果 eax - 11 == 0, 那么 eax == 11
je 0x002C1026
else
default
}
只有少數分支且case的值連續的時候,會用被判斷的值 - 最小值,然后 dec 減1,je 判斷
② 少數分支,但值不連續的時候
switch ( argc ) { case 10: printf("case 10 ! /r/n"); break; case 100: printf("case 100 ! /r/n"); break; default: printf("default ! /r/n"); break; } getchar();反匯編:

我們可以看到,直接就是cmp , je,類似于 if else 結構
只有少數分支,case的值不連續的時候,直接cmp , je
③ 當分支數量大于3個且連續的時候
switch ( argc ) { case 10: printf("case 10 ! /r/n"); break; case 11: printf("case 11 ! /r/n"); break; case 12: printf("case 12 ! /r/n"); break; case 13: printf("case 13 ! /r/n"); break; default: printf("default ! /r/n"); break; } getchar();反匯編:

依舊是第三行開始,這次貌似代碼不太一樣了,沒錯,這又是一個新姿勢了
eax = eax - 10 ( 0xA )
cmp eax, 3 這里是什么意思呢??? 為什么突然跟3比較?為嘛不是 4,5,6 ? 原來,這個時候為了達到更好的性能,編譯器替我們生成了一張表,跳轉表,這也是switch語句的精髓所在
大跳轉表(為嘛叫大表,后面解釋),其實就是一個地址數組
下標范圍:case最大值 - case最小值
大?。篶ase最大值 - case最小值 + 1
我們看后面的尋址方式,jmp dWord ptr ds:[ eax*4 + 0xFC1090 ],典型的數組尋址,這個0xFC1090就是跳轉表首地址,我們看看這個表,上圖紅色選中部分,我們發現里面存儲的值剛好是case的地址,我們理清下思路:
值 - case中的最小值 得到大表的索引,如果這個索引不在大表下標范圍內,ja (無符號大于跳轉)到 default,否則,jmp dword ptr ds:[ eax*4 + 0xFC1090 ],用這個索引在大表中取得 case 對應地址,直接過去。
這個大表是編譯器生成,我們不用去管,至于怎么生成?大家可以自己來嘗試實現一下。
④ 當分支數量大于3個且部分不連續的時候(差值較?。?/strong>
switch ( argc ) { case 10: printf("case 10 ! /r/n"); break; case 11: printf("case 11 ! /r/n"); break; case 13: printf("case 13 ! /r/n"); break; case 15: printf("case 12 ! /r/n"); break; default: printf("default ! /r/n"); break; } getchar();反匯編:

大表大?。?15 - 10 + 1 = 6 ,這個時候我們也只case了4個值,但是大表仍然被補齊成6個了,觀察發現,中間缺少的值被補成default
當我們的值為14時,14 - 0xA = 4, 4 < 5, [ 4*4 + 0xFC1090 ] = 0x00041075 -> default,是不是很機智。
⑤ 當分支數量大于3個且部分不連續的時候(差值較大)
switch ( argc ) { case 10: printf("case 10 ! /r/n"); break; case 11: printf("case 11 ! /r/n"); break; case 12: printf("case 12 ! /r/n"); break; case 19: printf("case 19 ! /r/n"); break; default: printf("default ! /r/n"); break; } getchar();
反匯編:

這個時候我們的兩個值的差值是7,這個時候發現又不一樣了,尋址方式變成了:movzx eax, byte ptr ds:[ eax + 0xE610A8 ],dword 變 byte 了,我們數據窗口中看一下,如圖選中內容,這就是小表,每個元素只占1個字節,小表大小也是最大值 19 - 最小值 10,接下來就是 jmp dword ptr ds:[ eax*4 + 0xE61094 ],這個當然,又是我們親愛的大表了,聯系上下文我們發現,小表里面存的就是大表的下標,為什么要這樣設計呢? 因為大表占四個字節,當差距比較大時,生成的大表自然也會變得很大,這個時候使用小表,可以更加節約內存。
⑥ 當分支數量大于3個且部分不連續的時候(差值非常大)
switch ( argc ) { case 10: printf("case 10 ! /r/n"); break; case 11: printf("case 11 ! /r/n"); break; case 12: printf("case 12 ! /r/n"); break; case 600: printf("case 600 ! /r/n"); break; default: printf("default ! /r/n"); break; } getchar();
反匯編:

最大值 600 - 最小值 10 = 590,,小表 590 字節 ? 這樣的話,小表就很大了,所以,又進行了改變,連續的部分依然使用第一種方法,不連續的使用 if else 結構,不再使用跳轉表了。
通過對 switch case 的一步步分析,我們發現情況還是很多的,可能不同的編譯器不一樣的優化,搞清楚原理,才能真正游刃有余。
新聞熱點
疑難解答