在网上查了一些资料说什么,三个判断以下选择if语句,三个判断以上选择switch语句,我查了一些资料都没有怎么说明这是为什么,所以今天从汇编角度对其进行了分析。
分析
if语句
C代码
int main()
{
int i = 10;
if(i==1)
{
printf("1");
}else if(i==2)
{
printf("2");
}else if(i==3)
{
printf("3");
}
}
汇编代码
00E12165 mov dword ptr [ebp-8],0Ah //存储局部变量 10
00E1216C cmp dword ptr [ebp-8],1 //比较 1跟10进行比较
00E12170 jne 00E12181 //不相等就跳转到 00E12181 地址(下一个if判断)
00E12172 push 0E17B94h
00E12177 call 00E110D2 //调用printf函数
00E1217C add esp,4
00E1217F jmp 00E121A9
00E12181 cmp dword ptr [ebp-8],2 //比较是否等于2
00E12185 jne 00E12196 //不等于就进行跳转到 00E12196 地址(下一一个if判断)
00E12187 push 0E17B98h
00E1218C call 00E110D2 //调用printf函数
00E12191 add esp,4
00E12194 jmp 00E121A9
00E12196 cmp dword ptr [ebp-8],3 //判断是否等于3
00E1219A jne 00E121A9 //不等于进行跳转
00E1219C push 0E17B9Ch
00E121A1 call 00E110D2 //调用printf函数
00E121A6 add esp,4
00E121A9 pop edi
观察以上汇编指令可以看出,if 语句就是一个一个的判断跳转。
switch语句
C代码
int main()
{
int i = 10;
switch (i)
{
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
default:
printf("error");
break;
}
}
汇编代码
009E2165 mov dword ptr [ebp-8],0Ah //保存局部变量 10
009E216C mov eax,dword ptr [ebp-8]
009E216F mov dword ptr [ebp+FFFFFF30h],eax
009E2175 cmp dword ptr [ebp+FFFFFF30h],1 // 1 跟 10 进行比较
009E217C je 009E2192 //相等就进行跳转
009E217E cmp dword ptr [ebp+FFFFFF30h],2 //2 跟 10 进行比较
009E2185 je 009E21A1 //相等进行跳转
009E2187 cmp dword ptr [ebp+FFFFFF30h],3 //3 跟 10 进行比较
009E218E je 009E21B0 //相等进行跳转
009E2190 jmp 009E21BF //都不相等进入default
009E2192 push 9E7B94h
009E2197 call 009E10D2
009E219C add esp,4
009E219F jmp 009E21CC
009E21A1 push 9E7B98h
009E21A6 call 009E10D2
009E21AB add esp,4
009E21AE jmp 009E21CC
009E21B0 push 9E7B9Ch
009E21B5 call 009E10D2
009E21BA add esp,4
009E21BD jmp 009E21CC
009E21BF push 9E7BA8h
009E21C4 call 009E10D2
009E21C9 add esp,4
009E21CC pop edi
分析
通过分析 if 和 switch的反汇编代码,发现都是通过一级一级的判断来实现的判断的,除了生成的语法不同其他流程都是一样的,那么不就说明了两个随便怎么使用都是一样的了吗?
其实是不一样的,编译器会通过不同的 switch 生成不同的汇编代码,下面继续分析 switch 反汇编代码。
4个分支switch语句
4个分支以上的 switch 分支语句,编译器将会对汇编生成规则进行优化了(不用记住 4个分支 这个值,因为不同编译器不一定一样)。
C代码
int main()
{
int i = 10;
switch (i)
{
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
case 4:
printf("4");
default:
printf("error");
break;
}
}
汇编代码
00672125 mov dword ptr [ebp-8],0Ah //保存局部变量 10
0067212C mov eax,dword ptr [ebp-8]
0067212F mov dword ptr [ebp+FFFFFF30h],eax
00672135 mov ecx,dword ptr [ebp+FFFFFF30h]
0067213B sub ecx,1
0067213E mov dword ptr [ebp+FFFFFF30h],ecx
00672144 cmp dword ptr [ebp+FFFFFF30h],3 //判断是否是默认default
0067214B ja 00672194 //如果是default,就跳转过去
0067214D mov edx,dword ptr [ebp+FFFFFF30h] //获取分支下标
00672153 jmp dword ptr [edx*4+006721B8h] //计算分支地址(这里计算出来的值就是 switch 分支地址表地址),然后跳转到对应地址。
0067215A push 677B94h
0067215F call 006710D2
00672164 add esp,4
00672167 jmp 006721A1
00672169 push 677B98h
0067216E call 006710D2
00672173 add esp,4
00672176 jmp 006721A1
00672178 push 677B9Ch
0067217D call 006710D2
00672182 add esp,4
00672185 jmp 006721A1
00672187 push 677BA0h
0067218C call 006710D2
00672191 add esp,4
00672194 push 677BA8h
00672199 call 006710D2
0067219E add esp,4
006721A1 pop edi
switch分支地址表
0x006721B8 5a 21 67 00 Z!g.
0x006721BC 69 21 67 00 i!g.
0x006721C0 78 21 67 00 x!g.
0x006721C4 87 21 67 00 ?!g.
通过观察上面的汇编可以得出,在 4 个分支的情况下 switch 生成的汇编代码就发生了变化了,减少了很多判断语句,而是通过查表得方式来查找具体得执行地址。
总结流程
- 判断是否默认分支,如果是就跳转到默认分支。
- 如果不是默认分支,通过计算在地址表中查询分支地址。
- 跳转到查询到得分支地址,执行分支代码。
总结
分支少于 3 个的情况下if跟switch生成的判断是一样的(不同编译器可能会不一样)。
分支多于 3 个的情况switch是通过查表得方式,来实现查询分支地址,减少了很多判断从而提高执行性能。
注意
上面只说明了正常情况下的switch生成汇编代码的情况,在其他情况也会生成其他格式的汇编代码,而且效率不一定比if语句执行效率高,比如case是1,2,3,10,50,500这种分支,它生成的汇编就和if语句是一样的,所以我们在写switch语句是尽量确保case的连续性。