关于Switch...case语句在汇编中的形式的探究

正向代码:

void main()
{
	int a, b;
	a = 0;
	b = 0;
	switch (a+b)
	{
	case 1: {
		printf("11");
		break;
	}
	default:
		break;
	}

对应的汇编代码:

	int a, b;
	a = 0;
00BC1838  mov         dword ptr [a],0  
	b = 0;
00BC183F  mov         dword ptr [b],0  
	switch (a+b)
00BC1846  mov         eax,dword ptr [a]  
00BC1849  add         eax,dword ptr [b]  
00BC184C  mov         dword ptr [ebp-0DCh],eax  
00BC1852  cmp         dword ptr [ebp-0DCh],1  
00BC1859  je          main+4Dh (0BC185Dh)  
00BC185B  jmp         main+5Ah (0BC186Ah)  
	{
	case 1: {
		printf("11");
00BC185D  push        offset string "11" (0BC7B30h)  
00BC1862  call        _printf (0BC104Bh)  
00BC1867  add         esp,4  
		break;
	}
	default:
		break;
	}
}
00BC186A  xor         eax,eax  

原函数中只有两个局部变量,在汇编代码可以看到编译器生产的第三个变量ebp-DC ,用来保存Switch中表达式的值。

Switch…case语句在汇编中的表示一般有四种情况:

分支个数在3个及以下的情况:

在上面基础上多加入两个分支看看汇编代码,

	switch (a+b)
000F4356  mov         eax,dword ptr [a]  
000F4359  add         eax,dword ptr [b]  
000F435C  mov         dword ptr [ebp-0DCh],eax  
000F4362  cmp         dword ptr [ebp-0DCh],1  
000F4369  je          main+5Fh (0F437Fh)  
000F436B  cmp         dword ptr [ebp-0DCh],2  
000F4372  je          main+6Eh (0F438Eh)  
000F4374  cmp         dword ptr [ebp-0DCh],3  
000F437B  je          main+7Dh (0F439Dh)  
000F437D  jmp         main+8Ah (0F43AAh)  
	{
	case 1: {
		printf("11");
000F437F  push        offset string "11" (0F7B30h)  
000F4384  call        _printf (0F104Bh)  
000F4389  add         esp,4  
		break;
000F438C  jmp         main+8Ah (0F43AAh)  
	}
	case 2: {
		printf("11");
000F438E  push        offset string "11" (0F7B30h)  
000F4393  call        _printf (0F104Bh)  
000F4398  add         esp,4  
		break;
000F439B  jmp         main+8Ah (0F43AAh)  
	}
	case 3: {
		printf("11");
000F439D  push        offset string "11" (0F7B30h)  
000F43A2  call        _printf (0F104Bh)  
000F43A7  add         esp,4  
		break;
	}
default:
		break;
	}

用cmp指令判断表达式值是否与case后面的相等,如相等就跳到对应的代码,全都不相等就通过Jmp跳到default处。

四条及以上连续分支的情况:

当有四条分支时,汇编代码就发生了变化:

int a, b;
	a = 0;
00534348  mov         dword ptr [a],0  
	b = 0;
0053434F  mov         dword ptr [b],0  
	switch (a+b)
00534356  mov         eax,dword ptr [a]  
00534359  add         eax,dword ptr [b]  
0053435C  mov         dword ptr [ebp-0DCh],eax  
00534362  mov         ecx,dword ptr [ebp-0DCh]  
00534368  sub         ecx,1  
0053436B  mov         dword ptr [ebp-0DCh],ecx  
00534371  cmp         dword ptr [ebp-0DCh],3  
00534378  ja          $LN7+0Dh (05343C1h)  
0053437A  mov         edx,dword ptr [ebp-0DCh]  
00534380  jmp         dword ptr [edx*4+5343D8h]  
	{
	case 1: {
		printf("11");
00534387  push        offset string "11" (0537B30h)  
0053438C  call        _printf (053104Bh)  
00534391  add         esp,4  
		break;
00534394  jmp         $LN7+0Dh (05343C1h)  
	}
	case 2: {
		printf("11");
00534396  push        offset string "11" (0537B30h)  
0053439B  call        _printf (053104Bh)  
005343A0  add         esp,4  
		break;
005343A3  jmp         $LN7+0Dh (05343C1h)  
	}
	case 3: {
		printf("11");
005343A5  push        offset string "11" (0537B30h)  
005343AA  call        _printf (053104Bh)  
005343AF  add         esp,4  
		break;
005343B2  jmp         $LN7+0Dh (05343C1h)  
	}
	case 4: {
		printf("11");
005343B4  push        offset string "11" (0537B30h)  
005343B9  call        _printf (053104Bh)  
005343BE  add         esp,4  
		break;
	}


	default:
		break;
	}
}
005343C1  xor         eax,eax  

局部变量ebp-0DC保存Switch表达式的值,然后用sub指令将ebp-0DCh值-1,ebp-0DCh在这里起索引的作用,减一后索引从0开始。由于下面case后面跟着的值是连续的,这里就直接用cmp判断switch中的值是否超过最大索引,超过则直接跳转到default处,如果再索引范围内,就通过

jmp dword ptr [edx*4+5343D8h]

跳转到对应的位置,这里[5343D8]保存的是一张跳转地址的表。

四条及以上不连续分支的情况1:

以上是在case后面的值连续的情况下,那么接下去看下值不连续的情况:

int a, b;
	a = 0;
00C14348  mov         dword ptr [a],0  
	b = 0;
00C1434F  mov         dword ptr [b],0  
	switch (a+b)
00C14356  mov         eax,dword ptr [a]  
00C14359  add         eax,dword ptr [b]  
00C1435C  mov         dword ptr [ebp-0DCh],eax  
00C14362  mov         ecx,dword ptr [ebp-0DCh]  
00C14368  sub         ecx,1  
00C1436B  mov         dword ptr [ebp-0DCh],ecx  
00C14371  cmp         dword ptr [ebp-0DCh],5  
00C14378  ja          $LN7+0Dh (0C143C1h)  
00C1437A  mov         edx,dword ptr [ebp-0DCh]  
00C14380  jmp         dword ptr [edx*4+0C143D8h]  
	{
	case 1: {
		printf("11");
00C14387  push        offset string "11" (0C17B30h)  
00C1438C  call        _printf (0C1104Bh)  
00C14391  add         esp,4  
		break;
00C14394  jmp         $LN7+0Dh (0C143C1h)  
	}
	case 2: {
		printf("11");
00C14396  push        offset string "11" (0C17B30h)  
00C1439B  call        _printf (0C1104Bh)  
00C143A0  add         esp,4  
		break;
00C143A3  jmp         $LN7+0Dh (0C143C1h)  
	}
	case 3: {
		printf("11");
00C143A5  push        offset string "11" (0C17B30h)  
00C143AA  call        _printf (0C1104Bh)  
00C143AF  add         esp,4  
		break;
00C143B2  jmp         $LN7+0Dh (0C143C1h)  
	}
	case 6: {
		printf("11");
00C143B4  push        offset string "11" (0C17B30h)  
00C143B9  call        _printf (0C1104Bh)  
00C143BE  add         esp,4  
		break;
	}


	default:
		break;
	}
}
00C143C1  xor         eax,eax  

汇编代码与上面相比没有什么变化,看下地址跳转[0C143D8]中保存的值

0x00C143D8  87 43 c1 00  ?C?.
0x00C143DC  96 43 c1 00  ?C?.
0x00C143E0  a5 43 c1 00  ?C?.
0x00C143E4  c1 43 c1 00  ?C?.
0x00C143E8  c1 43 c1 00  ?C?.
0x00C143EC  b4 43 c1 00  ?C?.

前三个对应的case 1,case2,case 3的地址,最后一个对应的case 6后面地址,我们并没有写case 4 和case 5,那么跳转表中保存的两个相同的地址实际上就是default的地址。

接下来看另一种情况:

正向代码:

void main()
{
	int a, b;
	a = 0;
	b = 0;
	switch (a+b)
	{
	case 100: {
		printf("11");
		break;
	}
	case 200: {
		printf("11");
		break;
	}
	case 300: {
		printf("11");
		break;
	}
	case 400: {
		printf("11");
		break;
	}
	case 1000: {
		printf("11");
		break;
	}


	default:
		break;
	}
}

四条及以上不连续分支的情况2:

以上我们知道在case后面的值不连续时编译器会用default的地址填充不存在的索引的跳转地址,使其连续。但是如果相差很多的话编译器还会逐条填充吗?

正向代码:

	switch (a+b)
	{
	case 1: {
		printf("11");
		break;
	}
	case 2: {
		printf("11");
		break;
	}
	case 3: {
		printf("11");
		break;
	}
	case 10: {
		printf("11");
		break;
	}
	default:
		break;
	}

这里的3和10次相差的蛮多的,看下此时的汇编代码:

	switch (a+b)
00A54E86  mov         eax,dword ptr [a]  
00A54E89  add         eax,dword ptr [b]  
00A54E8C  mov         dword ptr [ebp-0DCh],eax  
00A54E92  mov         ecx,dword ptr [ebp-0DCh]  
00A54E98  sub         ecx,1  
00A54E9B  mov         dword ptr [ebp-0DCh],ecx  
00A54EA1  cmp         dword ptr [ebp-0DCh],9  
00A54EA8  ja          $LN7+0Dh (0A54EF8h)  
00A54EAA  mov         edx,dword ptr [ebp-0DCh]  
00A54EB0  movzx       eax,byte ptr [edx+0A54F24h]  
00A54EB7  jmp         dword ptr [eax*4+0A54F10h]  
	{
	case 1: {
		printf("11");
00A54EBE  push        offset string "11" (0A57B30h)  
00A54EC3  call        _printf (0A5104Bh)  
00A54EC8  add         esp,4  
		break;
00A54ECB  jmp         $LN7+0Dh (0A54EF8h)  
	}
	case 2: {
		printf("11");
00A54ECD  push        offset string "11" (0A57B30h)  
00A54ED2  call        _printf (0A5104Bh)  
00A54ED7  add         esp,4  
		break;
00A54EDA  jmp         $LN7+0Dh (0A54EF8h)  
	}
	case 3: {
		printf("11");
00A54EDC  push        offset string "11" (0A57B30h)  
00A54EE1  call        _printf (0A5104Bh)  
00A54EE6  add         esp,4  
		break;
00A54EE9  jmp         $LN7+0Dh (0A54EF8h)  
	}
	case 10: {
		printf("11");
00A54EEB  push        offset string "11" (0A57B30h)  
00A54EF0  call        _printf (0A5104Bh)  
00A54EF5  add         esp,4  
		break;
	}
	default:
		break;
	}
}
00A54EF8  xor         eax,eax  

新多出了这两句:

00A54EB0 movzx eax,byte ptr [edx+0A54F24h]
00A54EB7 jmp dword ptr [eax*4+0A54F10h]

用到了eax,这里多了0A54F24h这张表,查看一下:

0x00A54F24  00 01 02 04  ....
0x00A54F28  04 04 04 04  ....
0x00A54F2C  04 03 cc cc  ..??
0x00A54F30  cc cc cc cc  ????
0x00A54F34  cc cc cc cc 

0 1 2对应的是case 1 ,case 2,case 3的索引, 然是6个04 ,再是 3

我们发现这张多出来的表是一张保存着索引值得表,其中不存在得case 4 --case 9的索引都替换为default的索引 也就是4,对比上一种情况,替换地址需要4个字节,而替换索引只需要1个字节,引入一张索引的表更加合理。

case后面的值相差很多情况:

几个case后面的值相差很多,

此时的汇编代码:

00184E7F  mov         dword ptr [b],0  
	switch (a+b)
00184E86  mov         eax,dword ptr [a]  
00184E89  add         eax,dword ptr [b]  
00184E8C  mov         dword ptr [ebp-0DCh],eax  
00184E92  cmp         dword ptr [ebp-0DCh],12Ch  
00184E9C  jg          main+71h (0184EC1h)  
00184E9E  cmp         dword ptr [ebp-0DCh],12Ch  
00184EA8  je          main+0A9h (0184EF9h)  
00184EAA  cmp         dword ptr [ebp-0DCh],64h  
00184EB1  je          main+8Bh (0184EDBh)  
00184EB3  cmp         dword ptr [ebp-0DCh],0C8h  
00184EBD  je          main+9Ah (0184EEAh)  
00184EBF  jmp         main+0D4h (0184F24h)  
00184EC1  cmp         dword ptr [ebp-0DCh],190h  
00184ECB  je          main+0B8h (0184F08h)  
00184ECD  cmp         dword ptr [ebp-0DCh],3E8h  
00184ED7  je          main+0C7h (0184F17h)  
00184ED9  jmp         main+0D4h (0184F24h)  
	{
	case 100: {
		printf("11");
00184EDB  push        offset string "11" (0187B30h)  
00184EE0  call        _printf (018104Bh)  
00184EE5  add         esp,4  
		break;
00184EE8  jmp         main+0D4h (0184F24h)  
	}
	case 200: {
		printf("11");
00184EEA  push        offset string "11" (0187B30h)  
00184EEF  call        _printf (018104Bh)  
00184EF4  add         esp,4  
		break;
00184EF7  jmp         main+0D4h (0184F24h)  
	}
	case 300: {
		printf("11");
00184EF9  push        offset string "11" (0187B30h)  
00184EFE  call        _printf (018104Bh)  
00184F03  add         esp,4  
		break;
00184F06  jmp         main+0D4h (0184F24h)  
	}
	case 400: {
		printf("11");
00184F08  push        offset string "11" (0187B30h)  
00184F0D  call        _printf (018104Bh)  
00184F12  add         esp,4  
		break;
00184F15  jmp         main+0D4h (0184F24h)  
	}
	case 1000: {
		printf("11");
00184F17  push        offset string "11" (0187B30h)  
	}
	case 1000: {
		printf("11");
00184F1C  call        _printf (018104Bh)  
00184F21  add         esp,4  
		break;
	}


	default:
		break;
	}
}
00184F24  xor         eax,eax  

00184E92 cmp dword ptr [ebp-0DCh],12Ch
00184E9C jg main+71h (0184EC1h) 这两句先判断表达式是否大于300,这里的300既不是最大值也不是最小值,而是中间值,采用二分法,如果大于300就继续与400和1000比较,小于300就和100,200比较。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值