c语言switch转化为汇编语言,C语言switch语句的汇编语言实现

这篇博客探讨了C语言switch语句在编译后的汇编实现,通过实例展示了微软cl8.0编译器如何处理不同case值的情况。编译器会根据case值的差异性进行优化,如使用跳转表或直接跳转,以平衡时间和空间效率。当case值差距较大时,编译器会生成跳转索引表以提高效率。
摘要由CSDN通过智能技术生成

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

一个好的编译器一定是一群顶尖软件高手们集体长时间创作的作品了,所以研究研究编译器的编译过程就是在向这些高手们学习。说到底,编译器也是一个很好的老师了,它可以为我们揭开高级语言实现的奥秘,为那些永不满足于表面现象的程序员深入掌握一门语言提供一个很好的途径。来看看微软的cl 8.0编译器是怎样来编译C语言的switch语句的,照例写一个测试的例子:

inttest()

{

inti,j;

i=j=0;

switch(i)

{

case1:j+=1;break;

case2:j+=2;break;

default:j+=5;

}

return0;

}

看看生成的汇编代码:

;5:

;6:switch(i)

movecx,DWORD PTR _i$[ebp]

movDWORD PTR tv64[ebp],ecx

;case 1:

cmpDWORD PTR tv64[ebp],1

jeSHORT $LN3@test

;case 2:

cmpDWORD PTR tv64[ebp],2

jeSHORT $LN2@test

;default:

jmpSHORT $LN1@test

$LN3@test:

;7:{

;8:case1:j+=1;break;

movedx,DWORD PTR _j$[ebp]

addedx,1

movDWORD PTR _j$[ebp],edx

jmpSHORT $LN4@test

$LN2@test:

;9:case2:j+=2;break;

moveax,DWORD PTR _j$[ebp]

addeax,2

movDWORD PTR _j$[ebp],eax

jmpSHORT $LN4@test

$LN1@test:

;10:default:j+=5;

movecx,DWORD PTR _j$[ebp]

addecx,5

movDWORD PTR _j$[ebp],ecx

$LN4@test:

;11:}

;12:

;13:return0;

xoreax,eax

;14:}

这段汇编代码是很好理解的,就是比较并跳转的过程。

下面看看一个比较复杂的switch

inttest()

{

inti,j;

i=j=0;

switch(i)

{

case1:j+=1;break;

case2:j+=2;break;

case3:j+=3;break;

case4:j+=4;break;

default:j+=10;

}

return0;

}

再来看看编译器生成的汇编代码:

;5:

;6:switch(i)

movecx,DWORD PTR _i$[ebp]

movDWORD PTR tv64[ebp],ecx

movedx,DWORD PTR tv64[ebp]

subedx,1

movDWORD PTR tv64[ebp],edx

cmpDWORD PTR tv64[ebp],3

jaSHORT $LN1@test

moveax,DWORD PTR tv64[ebp]

jmpDWORD 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:case1:j+=1;break;

movecx,DWORD PTR _j$[ebp]

addecx,1

movDWORD PTR _j$[ebp],ecx

jmpSHORT $LN6@test

$LN4@test:

;9:case2:j+=2;break;

movedx,DWORD PTR _j$[ebp]

addedx,2

movDWORD PTR _j$[ebp],edx

jmpSHORT $LN6@test

$LN3@test:

;10:case3:j+=3;break;

moveax,DWORD PTR _j$[ebp]

addeax,3

movDWORD PTR _j$[ebp],eax

jmpSHORT $LN6@test

$LN2@test:

;11:case4:j+=4;break;

movecx,DWORD PTR _j$[ebp]

addecx,4

movDWORD PTR _j$[ebp],ecx

jmpSHORT $LN6@test

$LN1@test:

;12:default:j+=10;

movedx,DWORD PTR _j$[ebp]

addedx,10;0000000aH

movDWORD 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的差别会带来怎样的差异:

inttest()

{

inti,j;

i=j=0;

switch(i)

{

case1:j+=1;break;

case2:j+=2;break;

case9:j+=3;break;

case15:j+=4;break;

default:j+=10;

}

return0;

}

来看看编译器产生的汇编代码:

_testPROC

;2:{

.....

;5:

;6:switch(i)

movecx,DWORD PTR _i$[ebp]

movDWORD PTR tv64[ebp],ecx

movedx,DWORD PTR tv64[ebp]

subedx,1

movDWORD PTR tv64[ebp],edx

cmpDWORD PTR tv64[ebp],14;0000000eH

jaSHORT $LN1@test

moveax,DWORD PTR tv64[ebp]

movzxecx,BYTE PTR $LN10@test[eax]

jmpDWORD 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:case1:j+=1;break;

movedx,DWORD PTR _j$[ebp]

addedx,1

movDWORD PTR _j$[ebp],edx

jmpSHORT $LN6@test

$LN4@test:

;9:case2:j+=2;break;

moveax,DWORD PTR _j$[ebp]

addeax,2

movDWORD PTR _j$[ebp],eax

jmpSHORT $LN6@test

$LN3@test:

;10:case9:j+=3;break;

movecx,DWORD PTR _j$[ebp]

addecx,3

movDWORD PTR _j$[ebp],ecx

jmpSHORT $LN6@test

$LN2@test:

;11:case15:j+=4;break;

movedx,DWORD PTR _j$[ebp]

addedx,4

movDWORD PTR _j$[ebp],edx

jmpSHORT $LN6@test

$LN1@test:

;12:default:j+=10;

moveax,DWORD PTR _j$[ebp]

addeax,10;0000000aH

movDWORD PTR _j$[ebp],eax

$LN6@test:

;13:}

;14:

;15:return0;

xoreax,eax

;16:}

movesp,ebp

popebp

ret0

$LN11@test:

DD$LN5@test

DD$LN4@test

DD$LN3@test

DD$LN2@test

DD$LN1@test

$LN10@test:

DB0

DB1

DB4

DB4

DB4

DB4

DB4

DB4

DB2

DB4

DB4

DB4

DB4

DB4

DB3

_testENDP

我们再来进一步的加大case值的差别:

inttest()

{

inti,j;

i=j=0;

switch(i)

{

case1:j+=1;break;

case100:j+=2;break;

case250:j+=3;break;

case550:j+=4;break;

default:j+=10;

}

return0;

}

来看看生成的汇编代码片断:

;5:

;6:switch(i)

movecx,DWORD PTR _i$[ebp]

movDWORD PTR tv64[ebp],ecx

;大于250则跳转到$LN10@test,即case 550的情况

cmpDWORD PTR tv64[ebp],250;000000faH

jgSHORT $LN10@test

case 250

cmpDWORD PTR tv64[ebp],250;000000faH

jeSHORT $LN3@test

;case 1

cmpDWORD PTR tv64[ebp],1

jeSHORT $LN5@test

;case 100

cmpDWORD PTR tv64[ebp],100;00000064H

jeSHORT $LN4@test

;其它情况跳转到default处的代码

jmpSHORT $LN1@test

$LN10@test:

;case 550

cmpDWORD PTR tv64[ebp],550;00000226H

jeSHORT $LN2@test

;其它情况跳转到default处的代码

jmpSHORT $LN1@test

$LN5@test:

哈哈,这与第一种情况很相似吧!好端端的代码被编译器处理得有点复杂,但是又滴水不漏。当然,编译器也是先判断了具体的值以后才对进行处理的。这时候,编译器一定在衡量了时间与空间的损失后作出这样的决定的,如果用第三种查表的方法继续编译空间损失会很大,需要一个很长的表格,在这种情况下,编译器选择了损失时间而节省空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值