在C語言中,if和switch是條件分支的重要組成部分。if的功能是計算判斷條件的值,根據返回的值的不一樣來決定跳轉到哪一個部分。值爲真則跳轉到if語句塊中,不然跳過if語句塊。下面來分析一個簡單的if實例:數組
if(argc > 0) { printf("argc > 0\n"); } if (argc <= 0) { printf("argc <= 0\n"); } printf("argc = %d\n", argc);
它對應的彙編代碼以下:優化
9: if(argc > 0) 00401028 cmp dword ptr [ebp+8],0 0040102C jle main+2Bh (0040103b) ;argc <= 0就跳轉到下一個if處 10: { 11: printf("argc > 0\n"); 0040102E push offset string "argc > 0\n" (0042003c) 00401033 call printf (00401090) 00401038 add esp,4 12: } 13: if (argc <= 0) ;argc > 0跳轉到後面的printf語句輸出argc的值 0040103B cmp dword ptr [ebp+8],0 0040103F jg main+3Eh (0040104e) 14: { 15: printf("argc <= 0\n"); 00401041 push offset string "argc <= 0\n" (0042002c) 00401046 call printf (00401090) 0040104B add esp,4 16: } 17: printf("argc = %d\n", argc); 0040104E mov eax,dword ptr [ebp+8] 00401051 push eax 00401052 push offset string "argc = %d\n" (0042001c) 00401057 call printf (00401090) 0040105C add esp,8
根據彙編代碼咱們看到,首先執行第一個if中的比較,jle表示當cmp獲得的結果≤0時會進行跳轉,第二個if在彙編中的跳轉條件是>0,從這個上面能夠看出在代碼執行過程中if轉換的條件判斷語句與if的判斷結果時相反的,也就是說cmp比較後不成立則跳轉,成立則向下執行。同時每一次跳轉都是到當前if語句的下一條語句。spa
下面來看看if...else...語句的跳轉。翻譯
if(argc > 0) { printf("argc > 0\n"); }else { printf("argc <= 0\n"); } printf("argc = %d\n", argc);
它所對應的彙編代碼以下:3d
00401028 cmp dword ptr [ebp+8],0 0040102C jle main+2Dh (0040103d) ;條件不知足則跳轉到else語句塊中 10: { 11: printf("argc > 0\n"); 0040102E push offset string "argc > 0\n" (0042003c) 00401033 call printf (00401090) 00401038 add esp,4 12: }else 0040103B jmp main+3Ah (0040104a);若是執行if語句塊就會執行這條語句跳出else語句塊 13: { 14: printf("argc <= 0\n"); 0040103D push offset string "argc <= 0\n" (0042002c) 00401042 call printf (00401090) 00401047 add esp,4 15: } 16: printf("argc = %d\n", argc); 0040104A mov eax,dword ptr [ebp+8]
上述的彙編代碼指出,對於if...else..語句,首先進行條件判斷,if表達式爲真,則繼續執行if快中的語句,而後利用jmp跳轉到else語句塊外,不然會利用jmp跳轉到else語句塊中,而後依次執行其後的每一句代碼。code
最後再來展現if...else if...else這種分支結構:blog
if(argc > 0) { printf("argc > 0\n"); }else if(argc < 0) { printf("argc < 0\n"); }else { printf("argc == 0\n"); } printf("argc = %d\n", argc);
彙編代碼以下:索引
9: if(argc > 0) 00401028 cmp dword ptr [ebp+8],0 0040102C jle main+2Dh (0040103d);條件不知足則會跳轉到下一句else if中 10: { 11: printf("argc > 0\n"); 0040102E push offset string "argc > 0\n" (00420f9c) 00401033 call printf (00401090) 00401038 add esp,4 12: }else if(argc < 0) 0040103B jmp main+4Fh (0040105f) ;當上述條件符合則執行這條語句跳出分支外,跳轉的地址正是else語句外的printf語句 0040103D cmp dword ptr [ebp+8],0 00401041 jge main+42h (00401052) 13: { 14: printf("argc < 0\n"); 00401043 push offset string "argc < 0\n" (0042003c) 00401048 call printf (00401090) 0040104D add esp,4 15: }else 00401050 jmp main+4Fh (0040105f) 16: { 17: printf("argc == 0\n"); 00401052 push offset string "argc <= 0\n" (0042002c) 00401057 call printf (00401090) 0040105C add esp,4 18: } 19: printf("argc = %d\n", argc); 0040105F mov eax,dword ptr [ebp+8]
經過彙編代碼能夠看到對於這種結構,會依次判斷每一個if語句中的條件,當有一個知足,執行完對應語句塊中的代碼後,會直接調轉到分支結構外部,當前面的條件都不知足則會執行else語句塊中的內容。這個邏輯結構在某些狀況下能夠利用if return if return 這種結構來替代。當某一條件知足時執行完對應的語句後直接返回而不執行其後的代碼。一條提高效率的作法是將最有可能知足的條件放在前面進行比較,這樣能夠減小比較次數,提高效率。內存
switch是另外一種比較經常使用的多分支結構,在使用上比較簡單,效率上也比if...else if...else高,下面將分析switch結構的實現資源
switch(argc)
{
case 1:
printf("argc = 1\n");
break;
case 2:
printf("argc = 2\n");
break;
case 3:
printf("argc = 3\n");
break;
case 4:
printf("argc = 4\n");
break;
case 5:
printf("argc = 5\n");
break;
case 6:
printf("argc = 6\n");
break;
default:
printf("else\n");
break;
}
對應的彙編代碼以下:
0040B798 mov eax,dword ptr [ebp+8] ;eax = argc 0040B79B mov dword ptr [ebp-4],eax 0040B79E mov ecx,dword ptr [ebp-4] ;ecx = eax 0040B7A1 sub ecx,1 0040B7A4 mov dword ptr [ebp-4],ecx 0040B7A7 cmp dword ptr [ebp-4],5 0040B7AB ja $L544+0Fh (0040b811) ;argc 》 5則跳轉到default處,至於爲何是5而不是6,看後面的說明 0040B7AD mov edx,dword ptr [ebp-4] ;edx = argc 0040B7B0 jmp dword ptr [edx*4+40B831h] 11: case 1: 12: printf("argc = 1\n"); 0040B7B7 push offset string "argc = 1\n" (00420fc0) 0040B7BC call printf (00401090) 0040B7C1 add esp,4 13: break; 0040B7C4 jmp $L544+1Ch (0040b81e) 14: case 2: 15: printf("argc = 2\n"); 0040B7C6 push offset string "argc = 2\n" (00420fb4) 0040B7CB call printf (00401090) 0040B7D0 add esp,4 16: break; 0040B7D3 jmp $L544+1Ch (0040b81e) 17: case 3: 18: printf("argc = 3\n"); 0040B7D5 push offset string "argc = 3\n" (00420fa8) 0040B7DA call printf (00401090) 0040B7DF add esp,4 19: break; 0040B7E2 jmp $L544+1Ch (0040b81e) 20: case 4: 21: printf("argc = 4\n"); 0040B7E4 push offset string "argc = 4\n" (00420f9c) 0040B7E9 call printf (00401090) 0040B7EE add esp,4 22: break; 0040B7F1 jmp $L544+1Ch (0040b81e) 23: case 5: 24: printf("argc = 5\n"); 0040B7F3 push offset string "argc < 0\n" (0042003c) 0040B7F8 call printf (00401090) 0040B7FD add esp,4 25: break; 0040B800 jmp $L544+1Ch (0040b81e) 26: case 6: 27: printf("argc = 6\n"); 0040B802 push offset string "argc <= 0\n" (0042002c) 0040B807 call printf (00401090) 0040B80C add esp,4 28: break; 0040B80F jmp $L544+1Ch (0040b81e) 29: default: 30: printf("else\n"); 0040B811 push offset string "argc = %d\n" (0042001c) 0040B816 call printf (00401090) 0040B81B add esp,4 31: break; 32: } 33: 34: return 0; 0040B81E xor eax,eax
上面的代碼中並無看到像if那樣,對每個條件都進行比較,其中有一句話 「jmp dword ptr [edx*4+40B831h]」 這句話從表面上看應該是取數組中的元素,再根據元素的值來進行跳轉,而這個元素在數組中的位置與eax也就是與argc的值有關,下面咱們跟蹤到數組中查看數組的元素值:
0040B831 B7 B7 40 00
0040B835 C6 B7 40 00
0040B839 D5 B7 40 00
0040B83D E4 B7 40 00
0040B841 F3 B7 40 00
0040B845 02 B8 40 00
經過對比能夠發現0x0040b7b7是case 1處的地址,後面的分別是case 二、case 三、case 四、case 五、case 6處的地址,每一個case中的break語句都翻譯爲了同一句話「jmp $L544+1Ch (0040b81e)」,因此從這能夠看出,在switch中,編譯器多增長了一個數組用於存儲每一個case對應的地址,根據switch中傳入的整數在數組中查到到對應的地址,直接經過這個地址跳轉到對應的位置,減小了比較操做,提高了效率。編譯器在處理switch時會首先校驗不知足全部case的狀況,當這種狀況發生時代碼調轉到default或者switch語句塊以外。而後將傳入的整數值減一(數組元素是從0開始計數)。最後根據參數值找到應該跳轉的位置。
上述的代碼case是從0~6依次遞增,這樣作確實可行,可是當咱們在case中的值並非依次遞增的話會怎樣?此時根據不一樣的狀況編譯器會作不一樣的處理。
1)通常任然會創建這樣的一個表,將case中出現的值填寫對應的跳轉地址,沒有出現的則將這個地址值填入default對應的地址或者switch語句結束的地址,好比當咱們上述的代碼去掉case 5, 這個時候填入的地址值以下圖所示:
2)若是每兩個case之間的差距大於6,或者case語句數小於4則不會採起這種作法,若是再採用這種方式,那麼會形成較大的資源消耗。這個時候編譯器會採用索引表的方式來進行地址的跳轉。
下面有這樣一個例子:
switch(argc) { case 1: printf("argc = 1\n"); break; case 2: printf("argc = 2\n"); break; case 5: printf("argc = 5\n"); break; case 6: printf("argc = 6\n"); break; case 255: printf("argc = 255\n"); default: printf("else\n"); break; }
它對應的彙編代碼以下:
0040B798 mov eax,dword ptr [ebp+8] 0040B79B mov dword ptr [ebp-4],eax 0040B79E mov ecx,dword ptr [ebp-4] ;到此eax = ecx = argc 0040B7A1 sub ecx,1 0040B7A4 mov dword ptr [ebp-4],ecx 0040B7A7 cmp dword ptr [ebp-4],0FEh 0040B7AE ja $L542+0Dh (0040b80b) ;當argc > 255則跳轉到default處 0040B7B0 mov eax,dword ptr [ebp-4] 0040B7B3 xor edx,edx 0040B7B5 mov dl,byte ptr (0040b843)[eax] 0040B7BB jmp dword ptr [edx*4+40B82Bh] 11: case 1: 12: printf("argc = 1\n"); 0040B7C2 push offset string "argc = 1\n" (00420fb4) 0040B7C7 call printf (00401090) 0040B7CC add esp,4 13: break; 0040B7CF jmp $L542+1Ah (0040b818) 14: case 2: 15: printf("argc = 2\n"); 0040B7D1 push offset string "argc = 3\n" (00420fa8) 0040B7D6 call printf (00401090) 0040B7DB add esp,4 16: break; 0040B7DE jmp $L542+1Ah (0040b818) 17: case 5: 18: printf("argc = 5\n"); 0040B7E0 push offset string "argc = 5\n" (00420f9c) 0040B7E5 call printf (00401090) 0040B7EA add esp,4 19: break; 0040B7ED jmp $L542+1Ah (0040b818) 20: case 6: 21: printf("argc = 6\n"); 0040B7EF push offset string "argc < 0\n" (0042003c) 0040B7F4 call printf (00401090) 0040B7F9 add esp,4 22: break; 0040B7FC jmp $L542+1Ah (0040b818) 23: case 255: 24: printf("argc = 255\n"); 0040B7FE push offset string "argc <= 0\n" (0042002c) 0040B803 call printf (00401090) 0040B808 add esp,4 25: default: 26: printf("else\n"); 0040B80B push offset string "argc = %d\n" (0042001c) 0040B810 call printf (00401090) 0040B815 add esp,4 27: break; 28: } 29: 30: return 0; 0040B818 xor eax,eax
這段代碼與上述的線性表相比較區別並不大,只是多了一句 「mov dl,byte ptr (0040b843)[eax]」 這彷佛又是一個數組,經過查看內存能夠知道這個數組的值分別爲:00 01 05 05 02 03 05 05 ... 04,下一句根據這些值在另一個數組中查找數據,咱們列出另一個數組的值:
C2 B7 40 00 D1 B7 40 00 E0 B7 40 00 EF B7 40 00 FE B7 40 00 0B B8 40 00
經過對比咱們發現,這些值分別是每一個case與default入口處的地址,編譯器先查找到每一個值在數組中對應的元素位置,而後根據這個位置值再在地址表中從、找到地址進行跳轉,這個過程能夠用下面的圖來表示:
這樣經過一個每一個元素佔一個字節的表,來表示對應的case在地址表中所對應的位置,從而跳轉到對應的地址,這樣經過對每一個case增長一個字節的內存消耗來達到,減小地址表對應的內存消耗。
在上述的彙編代碼中,是利用dl寄存器來存儲對應case在地址表中項,這樣就會產生一個問題,當case 值大於 255,也就是超出了一個字節的,超出了dl寄存器的表示範圍時,又該如何來進行跳轉這個時候編譯器會採用斷定樹的方式來進行斷定,在根節點保存的是全部case值的中位數, 左子樹都是大於這個大於這個值的數,右字數是小於這個值的數,經過每次的比較來獲得正確的地址。好比下面的這個斷定樹:
首先與10進行比較,根據與10 的大小關係進入左子樹或者右子樹,再看看左右子樹的分支是否不大於3,若不大於3則直接轉化爲對應的if...else if... else結構,大於3則檢測分支是否知足上述的優化條件,知足則進行對應的地址表或者索引表的優化,不然會再次對子樹進行優化,以便減小比較次數。