C语言switch语句的汇编语言实现

 int test()
{
    int i,j;
    i=j=0;
   switch(i)
    {
       case 1:j+=1;break;
       case 2:j+=2;break;
       default:j+=5;
    }
    return 0;
}
看看生成的汇编代码:
; 5    : 
; 6    :    switch(i)
    mov ecx, DWORD PTR _i$[ebp]
    mov DWORD PTR tv64[ebp], ecx
;case 1:
cmp DWORD PTR tv64[ebp], 1
    je  SHORT $LN3@test
;case 2:
cmp DWORD PTR tv64[ebp], 2
    je  SHORT $LN2@test
;default:
    jmp SHORT $LN1@test
$LN3@test:
; 7    :    {
; 8    :       case 1:j+=1;break;
    mov edx, DWORD PTR _j$[ebp]
    add edx, 1
    mov DWORD PTR _j$[ebp], edx
    jmp SHORT $LN4@test
$LN2@test:
; 9    :       case 2:j+=2;break;
    mov eax, DWORD PTR _j$[ebp]
    add eax, 2
    mov DWORD PTR _j$[ebp], eax
    jmp SHORT $LN4@test
$LN1@test:
; 10   :       default:j+=5;
    mov ecx, DWORD PTR _j$[ebp]
    add ecx, 5
    mov DWORD PTR _j$[ebp], ecx
$LN4@test:
; 11   :    }
; 12   : 
; 13   :    return 0;
    xor eax, eax
; 14   : }
这段汇编代码是很好理解的,就是比较并跳转的过程。
下面看看一个比较复杂的switch
int test()
{
    int i,j;
    i=j=0;
   switch(i)
    {
       case 1:j+=1;break;
       case 2:j+=2;break;
       case 3:j+=3;break;
       case 4:j+=4;break;
       default:j+=10;
    }
    return 0;
}
再来看看编译器生成的汇编代码:
; 5    : 
; 6    :    switch(i)
    mov ecx, DWORD PTR _i$[ebp]
    mov DWORD PTR tv64[ebp], ecx
    mov edx, DWORD PTR tv64[ebp]
    sub edx, 1
    mov DWORD PTR tv64[ebp], edx
    cmp DWORD PTR tv64[ebp], 3
    ja  SHORT $LN1@test
    mov eax, DWORD PTR tv64[ebp]
    jmp DWORD PTR $LN10@test[eax*4]
;以上的汇编代码相当于以下的伪指令:
if(i-1 > 3)
{
    goto $LN1@test;//跳转到default
}
else
{
    goto $LN10@test[i-1];//case 1,case 2,case 3,case 4的情况
}
$LN10@test是一个跳转表,通过它就可以跳转到相应的处理代码中去,看来微软的编译器还是很聪明的,会在编译时对case的值作一些判断,这也就是所谓的优化吧。
$LN5@test:
; 7    :    {
; 8    :       case 1:j+=1;break;
    mov ecx, DWORD PTR _j$[ebp]
    add ecx, 1
    mov DWORD PTR _j$[ebp], ecx
    jmp SHORT $LN6@test
$LN4@test:
; 9    :       case 2:j+=2;break;
    mov edx, DWORD PTR _j$[ebp]
    add edx, 2
    mov DWORD PTR _j$[ebp], edx
    jmp SHORT $LN6@test
$LN3@test:
; 10   :       case 3:j+=3;break;
    mov eax, DWORD PTR _j$[ebp]
    add eax, 3
    mov DWORD PTR _j$[ebp], eax
    jmp SHORT $LN6@test
$LN2@test:
; 11   :       case 4:j+=4;break;
    mov ecx, DWORD PTR _j$[ebp]
    add ecx, 4
    mov DWORD PTR _j$[ebp], ecx
    jmp SHORT $LN6@test
$LN1@test:
; 12   :       default:j+=10;
    mov edx, DWORD PTR _j$[ebp]
    add edx, 10                  ; 0000000aH
    mov DWORD PTR _j$[ebp], edx
$LN6@test:
; 13   :    }
......
$LN10@test:
    DD  $LN5@test          ;case 1
    DD  $LN4@test         ;case 2
    DD  $LN3@test         ;case 3
    DD  $LN2@test         ;case 4
下面来看看加大各case的差别会带来怎样的差异:
int test()
{
    int i,j;
    i=j=0;
   switch(i)
    {
       case 1:j+=1;break;
       case 2:j+=2;break;
       case 9:j+=3;break;
       case 15:j+=4;break;
       default:j+=10;
    }
    return 0;
}
来看看编译器产生的汇编代码:
_test  PROC
; 2    : {
.....
; 5    : 
; 6    :    switch(i)
    mov ecx, DWORD PTR _i$[ebp]
    mov DWORD PTR tv64[ebp], ecx
    mov edx, DWORD PTR tv64[ebp]
    sub edx, 1
    mov DWORD PTR tv64[ebp], edx
    cmp DWORD PTR tv64[ebp], 14         ; 0000000eH
    ja  SHORT $LN1@test
    mov eax, DWORD PTR tv64[ebp]
    movzx  ecx, BYTE PTR $LN10@test[eax]
    jmp DWORD PTR $LN11@test[ecx*4]
以上的语句相当于下面的代码:
if(i-1>=14)
{
    goto $LN1@test ;//跳转到switch语句的defaul分支
}
else
{
    index = $LN10@test[i-1];//$LN10@test为跳转索引表
    goto $LN11@test[index];//$LN11@teest为跳转表
}
可见,对于case的值有较大的差别的情况编译器还会新增一个跳转索引表,先通过case值找到对应的跳转表的索引,然后通过索引再找到跳转表中的跳转地址。
$LN5@test:
; 7    :    {
; 8    :       case 1:j+=1;break;
    mov edx, DWORD PTR _j$[ebp]
    add edx, 1
    mov DWORD PTR _j$[ebp], edx
    jmp SHORT $LN6@test
$LN4@test:
; 9    :       case 2:j+=2;break;
    mov eax, DWORD PTR _j$[ebp]
    add eax, 2
    mov DWORD PTR _j$[ebp], eax
    jmp SHORT $LN6@test
$LN3@test:
; 10   :       case 9:j+=3;break;
    mov ecx, DWORD PTR _j$[ebp]
    add ecx, 3
    mov DWORD PTR _j$[ebp], ecx
    jmp SHORT $LN6@test
$LN2@test:
; 11   :       case 15:j+=4;break;
    mov edx, DWORD PTR _j$[ebp]
    add edx, 4
    mov DWORD PTR _j$[ebp], edx
    jmp SHORT $LN6@test
$LN1@test:
; 12   :       default:j+=10;
    mov eax, DWORD PTR _j$[ebp]
    add eax, 10                  ; 0000000aH
    mov DWORD PTR _j$[ebp], eax
$LN6@test:
; 13   :    }
; 14   : 
; 15   :    return 0;
    xor eax, eax
; 16   : }
    mov esp, ebp
    pop ebp
    ret 0
$LN11@test:
    DD  $LN5@test
    DD  $LN4@test
    DD  $LN3@test
    DD  $LN2@test
    DD  $LN1@test
$LN10@test:
    DB  0
    DB  1
    DB  4
    DB  4
    DB  4
    DB  4
    DB  4
    DB  4
    DB  2
    DB  4
    DB  4
    DB  4
    DB  4
    DB  4
    DB  3
_test  ENDP
我们再来进一步的加大case值的差别:
int test()
{
    int i,j;
    i=j=0;
    switch(i)
    {
       case 1:j+=1;break;
       case 100:j+=2;break;
       case 250:j+=3;break;
       case 550:j+=4;break;
       default:j+=10;
    }
    return 0;
}
来看看生成的汇编代码片断:
; 5    : 
; 6    :    switch(i)
    mov ecx, DWORD PTR _i$[ebp]
    mov DWORD PTR tv64[ebp], ecx
;大于250则跳转到$LN10@test,即case 550 的情况
    cmp DWORD PTR tv64[ebp], 250    ; 000000faH
    jg  SHORT $LN10@test
case 250
    cmp DWORD PTR tv64[ebp], 250    ; 000000faH
    je  SHORT $LN3@test
;case 1
    cmp DWORD PTR tv64[ebp], 1
    je  SHORT $LN5@test
;case 100
    cmp DWORD PTR tv64[ebp], 100    ; 00000064H
    je  SHORT $LN4@test
;其它情况跳转到default处的代码
    jmp SHORT $LN1@test
$LN10@test:
;case 550
    cmp DWORD PTR tv64[ebp], 550    ; 00000226H
    je  SHORT $LN2@test
;其它情况跳转到default处的代码
    jmp SHORT $LN1@test
$LN5@test:
哈哈,这与第一种情况很相似吧!好端端的代码被编译器处理得有点复杂,但是又滴水不漏。当然,编译器也是先判断了具体的值以后才对进行处理的。这时候,编译器一定在衡量了时间与空间的损失后作出这样的决定的,如果用第三种查表的方法继续编译空间损失会很大,需要一个很长的表格,在这种情况下,编译器选择了损失时间而节省空间。

转载于:https://my.oschina.net/u/2407826/blog/474339

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值