动态线条
动态线条end

汇编:C语言switch转汇编

switch概述

根据不同的表达式值,执行不同的分支块代码

常见应用方式如下

switch(表达式){
	case xx1:{
		//功能代码
		break;
	}
	case xx2:{
		//功能代码
		break;
	}
	....
}

根据case后边紧跟的常量值的特点(下文简称case常量),编译器会根据一定取舍,将switch的性能尽可能的优化,常见switch的汇编代码有下几种。


第一种:case常量连续型,但case语句较少

C语言代码

#include <stdio.h>

void Fun(int n){
	switch (n){
		case 1:{
			printf("1");
			break;
		}
		case 2:{
			printf("2");
			break;
		}
		default:{
			printf("default");
			break;
		}
	}
}

int main(){
	Fun(1);
	return 0;
}

汇编代码

被调函数开头汇编代码

image-20220215185747180

switch汇编代码

image-20220215190913879

;提升堆栈
009F3C00  push        ebp  
009F3C01  mov         ebp,esp  
009F3C03  sub         esp,0C4h  
;保存现场
009F3C09  push        ebx  
009F3C0A  push        esi  
009F3C0B  push        edi  
;初始化堆栈空间
009F3C0C  lea         edi,[ebp+FFFFFF3Ch]  
009F3C12  mov         ecx,31h  
009F3C17  mov         eax,0CCCCCCCCh  
009F3C1C  rep stos    dword ptr es:[edi]  
;switch功能代码
009F3C1E  mov         eax,dword ptr [ebp+8]  
009F3C21  mov         dword ptr [ebp+FFFFFF3Ch],eax  
009F3C27  cmp         dword ptr [ebp+FFFFFF3Ch],1  ;判断
009F3C2E  je          009F3C3B  ;跳转
009F3C30  cmp         dword ptr [ebp+FFFFFF3Ch],2  ;判断
009F3C37  je          009F3C54  ;跳转
009F3C39  jmp         009F3C6D  
009F3C3B  mov         esi,esp  
009F3C3D  push        9F58A8h  
009F3C42  call        dword ptr ds:[009F9114h]  
009F3C48  add         esp,4  
009F3C4B  cmp         esi,esp  
009F3C4D  call        009F113B  
009F3C52  jmp         009F3C84  
009F3C54  mov         esi,esp  
009F3C56  push        9F58ACh  
009F3C5B  call        dword ptr ds:[009F9114h]  
009F3C61  add         esp,4  
009F3C64  cmp         esi,esp  
009F3C66  call        009F113B  
009F3C6B  jmp         009F3C84  
009F3C6D  mov         esi,esp  
009F3C6F  push        9F59C4h  
009F3C74  call        dword ptr ds:[009F9114h]  
009F3C7A  add         esp,4  
009F3C7D  cmp         esi,esp  
009F3C7F  call        009F113B  
;switch功能结束,开始恢复现场
009F3C84  pop         edi  
009F3C85  pop         esi  
009F3C86  pop         ebx  
009F3C87  add         esp,0C4h  
009F3C8D  cmp         ebp,esp  
009F3C8F  call        009F113B  
009F3C94  mov         esp,ebp  
009F3C96  pop         ebp  
009F3C97  ret  

通过上述图片可以看出,如果case个数较少,那么形成的汇编代码与if分支语句效果等同,效率上并不会提高很多。

第二种:case常量连续型,但case语句较多

C语言代码

#include <stdio.h>

void Fun(int n){
	switch (n){
		case 1:{
			printf("1");
			break;
		}
		case 2:{
			printf("2");
			break;
		}
		case 3:{
			printf("3");
			break;
		}
		case 4:{
			printf("4");
			break;
		}
		case 5:{
			printf("5");
			break;
		}
		default:{
			printf("default");
			break;
		}
	}
}

int main(){
	Fun(1);
	return 0;
}

汇编代码

在看switch汇编之前,需要了解下大表的概念,如果case的分支过多,那么编译器将会生成一个记录各个case地址的大表。

image-20220215184911861

image-20220215175432959

image-20220215175801785


接下来让我们观察大表的具体值,将大表基址拖拽到内存模块中

image-20220215175929739

根据大表进行跳转

image-20220215184536725

image-20220215184611748

综上,switch产生的汇编代码如下

;提升堆栈
006A3C00  push        ebp  
006A3C01  mov         ebp,esp  
006A3C03  sub         esp,0C4h  
;保存现场
006A3C09  push        ebx  
006A3C0A  push        esi  
006A3C0B  push        edi  
;初始化堆栈
006A3C0C  lea         edi,[ebp+FFFFFF3Ch]  
006A3C12  mov         ecx,31h  
006A3C17  mov         eax,0CCCCCCCCh  
006A3C1C  rep stos    dword ptr es:[edi]  
;函数功能代码
006A3C1E  mov         eax,dword ptr [ebp+8]  ;将函数参数放入eax寄存器中
006A3C21  mov         dword ptr [ebp+FFFFFF3Ch],eax  
006A3C27  mov         ecx,dword ptr [ebp+FFFFFF3Ch]  
006A3C2D  sub         ecx,1  ;参数值减一
006A3C30  mov         dword ptr [ebp+FFFFFF3Ch],ecx  
006A3C36  cmp         dword ptr [ebp+FFFFFF3Ch],4  ;将处理后的参数值与常数4进行对比(4是case常量中的最大值减一)
006A3C3D  ja          006A3CCD  ;如果比较结果是大于,证明要执行的代码块不存在,因此直接跳转到default
006A3C43  mov         edx,dword ptr [ebp+FFFFFF3Ch]  
006A3C49  jmp         dword ptr [edx*4+006A3CF8h]  ;否则根据大表计算应该跳到哪个地址
;对应的各个case代码块
$LN6:
006A3C50  mov         esi,esp  
006A3C52  push        6A58A8h  
006A3C57  call        dword ptr ds:[006A9114h]  
006A3C5D  add         esp,4  
006A3C60  cmp         esi,esp  
006A3C62  call        006A113B  
006A3C67  jmp         006A3CE4  
$LN5:
006A3C69  mov         esi,esp  
006A3C6B  push        6A58ACh  
006A3C70  call        dword ptr ds:[006A9114h]  
006A3C76  add         esp,4  
006A3C79  cmp         esi,esp  
006A3C7B  call        006A113B  
006A3C80  jmp         006A3CE4  
$LN4:
006A3C82  mov         esi,esp  
006A3C84  push        6A58B0h  
006A3C89  call        dword ptr ds:[006A9114h]  
006A3C8F  add         esp,4  
006A3C92  cmp         esi,esp  
006A3C94  call        006A113B  
006A3C99  jmp         006A3CE4  
$LN3:
006A3C9B  mov         esi,esp  
006A3C9D  push        6A58B4h  
006A3CA2  call        dword ptr ds:[006A9114h]  
006A3CA8  add         esp,4  
006A3CAB  cmp         esi,esp  
006A3CAD  call        006A113B  
006A3CB2  jmp         006A3CE4  
$LN2:
006A3CB4  mov         esi,esp  
006A3CB6  push        6A5918h  
006A3CBB  call        dword ptr ds:[006A9114h]  
006A3CC1  add         esp,4  
006A3CC4  cmp         esi,esp  
006A3CC6  call        006A113B  
006A3CCB  jmp         006A3CE4  
006A3CCD  mov         esi,esp  
006A3CCF  push        6A591Ch  
006A3CD4  call        dword ptr ds:[006A9114h]  
006A3CDA  add         esp,4  
006A3CDD  cmp         esi,esp  
006A3CDF  call        006A113B  
;恢复现场,结束函数调用
006A3CE4  pop         edi  
006A3CE5  pop         esi  
006A3CE6  pop         ebx  
006A3CE7  add         esp,0C4h  
006A3CED  cmp         ebp,esp  
006A3CEF  call        006A113B  
006A3CF4  mov         esp,ebp  
006A3CF6  pop         ebp  
006A3CF7  ret  
006A3CF8  push        eax  
006A3CF9  cmp         al,6Ah  
006A3CFB  add         byte ptr [ecx+3Ch],ch  
006A3CFE  push        0  
006A3D00  cmp         byte ptr [edx+ebp*2],0  
006A3D04  wait  
006A3D05  cmp         al,6Ah  
006A3D07  add         byte ptr [esp+edi+CCCC006Ah],dh 

第三种:case常量不连续型

1)大表中缺少的位置可能由default的地址来填充

#include <stdio.h>

void Fun(int n){
	switch (n){
		case 1:{
			printf("1");
			break;
		}
		case 2:{
			printf("2");
			break;
		}
		case 3:{
			printf("3");
			break;
		}
		case 4:{
			printf("4");
			break;
		}
		case 5:{
			printf("5");
			break;
		}
		case 6:{
			printf("6");
			break;
		}
		case 10:{
			printf("10");
			break;
		}
		default:{
			printf("default");
			break;
		}
	}
}

int main(){
	Fun(1);
	return 0;
}

将大表基址拖拽到内存模块中,查看内存中大表的存储内容

image-20220215192317567

其中一部分直接指向对应的case地址

image-20220215192452295

但是我们能够看见,其中一部分缺失的case常量,如case 7,case 8,case 9三个位置的值相同,都指向了default代码块的地址

image-20220215192758505

综上:如果case常量不连续个数比较少,那么缺少的位置可由default代码块地址填充


2)大表中缺少的位置过多时,产生小表,利用小表来对大表进行优化

大表:记录了每一个case块的地址,一条记录占用四个字节

小表:记录了使用第几个大表数据,一条记录占用一个字节

#include <stdio.h>

void Fun(int n){
	switch (n){
		case 1:{
			printf("1");
			break;
		}
		case 5:{
			printf("5");
			break;
		}
		case 6:{
			printf("6");
			break;
		}
		case 10:{
			printf("10");
			break;
		}
		default:{
			printf("default");
			break;
		}
	}
}

int main(){
	Fun(1);
	return 0;
}

大表(低地址)一般在小表(高地址)上方位置

image-20220215200032706

由于switch中的case常量范围是case1 - case 10,再加上default默认代码块,一共是十个块,位置从0开始计数

image-20220215221556579

查询顺序

1)根据 switch中的参数,计算{参数 - 1},查询小表中对应的值

2)根据小表查询结果,在大表中查询case对应地址

3)根据case地址,跳转到对应代码块,执行对应代码块命令


除了上述三种情况外,switch还有一个需要注意的地方

上述实验过程,都是从1开始,那么如果出现case的范围中最小的值就比较大(如:100、101、102、103),会出现什么情况?

//case常量范围100 - 103
#include <stdio.h>

void Fun(int n){
	switch (n){
		case 100:{
			printf("100");
			break;
		}
		case 101:{
			printf("101");
			break;
		}
		case 102:{
			printf("102");
			break;
		}
		case 103:{
			printf("103");
			break;
		}
		default:{
			printf("default");
			break;
		}
	}
}

int main(){
	Fun(1);
	return 0;
}

其中汇编代码有sub ecx,64H的一条语句,将其转换成十进制:ecx = ecx - 100

image-20220215222145680

case范围中最小值是1的时候,sub eax,1H

image-20220215221907164

综上可以看出在case的取值范围中,最小值在大表中永远是处在零的位置


总结

1)如果case连续且分支不多,那么汇编代码将生成类似if跳转的汇编代码

2)如果case连续但是分支很多,那么编译器将为其生成一个大表,用来记录每一个case的地址,从而通过计算,直接跳转到所需执行的命令处

3)如果case不连续(缺少的case不多),但是分支较多,那么编译器会在大表的空缺处,填写default代码块的地址

4)如果case不连续(缺少的case较多),但是分支还比较多,那么编译器会将大表进行压缩,并且在大表的下边生成一个小表,用来对大表进行存储空间的优化

5)如果case不连续(缺少的case较多),但是分支较少,那么编译器将舍弃生成大表,直接按照if跳转的形式生成汇编指令(最后一条情况未实验,感兴趣可自行实验)

应用注意

在使用switch的时候,应该尽量使case连续。如果switch的分支较少,那么可以直接考虑使用if,而不使用switch

posted @ 2022-02-15 22:43  v1v1v1  阅读(504)  评论(0编辑  收藏  举报