if和switch的原理

  在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則檢測分支是否知足上述的優化條件,知足則進行對應的地址表或者索引表的優化,不然會再次對子樹進行優化,以便減小比較次數。

相關文章
相關標籤/搜索