我们先来看if else语句编译是怎么实现的
上代码
void main()
{
int a=4;
if (1==a)
{
printf("1");
}
else if(2==a)
{
printf("2");
}
else if(3==a)
{
printf("3");
}
else
{
printf("4");
}
}
这里我们先试着三次判断,通过反汇编来看看编译器怎实现的
int a=4;
004113EE mov dword ptr [a],4 //将4放入a中
if (1==a)
004113F5 cmp dword ptr [a],1 //1与4比较
004113F9 jne main+44h (411414h) //相同为1则顺序执行下面,否则跳转到下一个if处
{
printf("1");
004113FB mov esi,esp
004113FD push offset string "1" (415648h)
00411402 call dword ptr [__imp__printf (4182B8h)]
00411408 add esp,4
0041140B cmp esi,esp
0041140D call @ILT+305(__RTC_CheckEsp) (411136h)
00411412 jmp main+99h (411469h)
}
else if(2==a)
00411414 cmp dword ptr [a],2 //第一个if为假则这里执行比较
00411418 jne main+63h (411433h) //比较为真顺序执行,为假跳到下一个else if
{
printf("2");
0041141A mov esi,esp
0041141C push offset string "2" (415644h)
00411421 call dword ptr [__imp__printf (4182B8h)]
00411427 add esp,4
0041142A cmp esi,esp 0041142C call @ILT+305(__RTC_CheckEsp) (411136h)
00411431 jmp main+99h (411469h)
}
else if(3==a)
00411433 cmp dword ptr [a],3 //上一个if为假这里开始比较
00411437 jne main+82h (411452h) //比较为真顺序执行,为假跳转到最后一个else
{
printf("3");
00411439 mov esi,esp
0041143B push offset string "3" (415640h)
00411440 call dword ptr [__imp__printf (4182B8h)]
00411446 add esp,4
00411449 cmp esi,esp
0041144B call @ILT+305(__RTC_CheckEsp) (411136h)
}
else
00411450 jmp main+99h (411469h) //这个跳转指令是当上一个else if执行后顺序执行下来到这一条指令,意思是直接跳转到else语句}后面的语句
{
printf("4");
00411452 mov esi,esp
00411454 push offset string "0" (41563Ch)
00411459 call dword ptr [__imp__printf (4182B8h)]
0041145F add esp,4
00411462 cmp esi,esp
00411464 call @ILT+305(__RTC_CheckEsp) (411136h)
}
}
这里通过查看反汇编可以看出if else 结构是顺序比较(除去最后一个else不比较),特别是当产生的分支结构较多时,效率很低。
那可能有人问那 if if if。。。结构是怎么的
int a=3;
004113EE mov dword ptr [a],3
if (1==a)
004113F5 cmp dword ptr [a],1
004113F9 jne main+42h (411412h)
{
printf("1");
004113FB mov esi,esp
004113FD push offset string "1" (415648h)
00411402 call dword ptr [__imp__printf (4182B8h)]
00411408 add esp,4
0041140B cmp esi,esp
0041140D call @ILT+305(__RTC_CheckEsp) (411136h)
}
if(2==a)
00411412 cmp dword ptr [a],2
00411416 jne main+5Fh (41142Fh)
{
printf("2");
00411418 mov esi,esp
0041141A push offset string "2" (415644h)
0041141F call dword ptr [__imp__printf (4182B8h)]
00411425 add esp,4
00411428 cmp esi,esp
0041142A call @ILT+305(__RTC_CheckEsp) (411136h)
}
if(3==a)
0041142F cmp dword ptr [a],3
00411433 jne main+7Ch (41144Ch)
{
printf("3");
00411435 mov esi,esp
00411437 push offset string "3" (415640h)
0041143C call dword ptr [__imp__printf (4182B8h)]
00411442 add esp,4
00411445 cmp esi,esp
00411447 call @ILT+305(__RTC_CheckEsp) (411136h)
}
if (4==a)
0041144C cmp dword ptr [a],4
00411450 jne main+99h (411469h)
{
printf("4");
00411452 mov esi,esp
00411454 push offset string "0" (41563Ch)
00411459 call dword ptr [__imp__printf (4182B8h)]
0041145F add esp,4
00411462 cmp esi,esp
00411464 call @ILT+305(__RTC_CheckEsp) (411136h)
}
这里我们将每一个else去掉,都写成if结构,通过反汇编不难看出,差距不大只是最后一个if多比较了一次,但是依旧是多分支结构时耗损多。
那我们再看看switch case结构又有什么优点?
上代码
int a=3;
switch(a)
{
case 1:
printf("1");
break;
case 2:
printf("2");
break;
case 3:
printf("3");
break;
default:
printf("0");
}
这里先选择一下三个case语句,注意这里是switch case 的一个重点
反汇编
int a=3;
004113EE mov dword ptr [a],3
```
switch(a)
004113F5 mov eax,dword ptr [a]
004113F8 mov dword ptr [ebp-0DCh],eax
004113FE cmp dword ptr [ebp-0DCh],1
00411405 je main+4Bh (41141Bh)
00411407 cmp dword ptr [ebp-0DCh],2
0041140E je main+64h (411434h)
00411410 cmp dword ptr [ebp-0DCh],3
00411417 je main+7Dh (41144Dh)
00411419 jmp main+96h (411466h)
```
{
case 1:
printf("1");
0041141B mov esi,esp
0041141D push offset string "1" (415648h)
00411422 call dword ptr [__imp__printf (4182B8h)]
00411428 add esp,4
0041142B cmp esi,esp
0041142D call @ILT+305(__RTC_CheckEsp) (411136h)
break;
00411432 jmp main+0ADh (41147Dh)
case 2:
printf("2");
00411434 mov esi,esp
00411436 push offset string "2" (415644h)
0041143B call dword ptr [__imp__printf (4182B8h)]
00411441 add esp,4
00411444 cmp esi,esp
00411446 call @ILT+305(__RTC_CheckEsp) (411136h)
break;
0041144B jmp main+0ADh (41147Dh)
case 3:
printf("3");
0041144D mov esi,esp
0041144F push offset string "3" (415640h)
00411454 call dword ptr [__imp__printf (4182B8h)]
0041145A add esp,4
0041145D cmp esi,esp
0041145F call @ILT+305(__RTC_CheckEsp) (411136h)
break;
00411464 jmp main+0ADh (41147Dh)
default:
printf("0");
00411466 mov esi,esp
00411468 push offset string "0" (41563Ch)
0041146D call dword ptr [__imp__printf (4182B8h)]
00411473 add esp,4
00411476 cmp esi,esp
00411478 call @ILT+305(__RTC_CheckEsp) (411136h)
}
通过反汇编我们查看到,编译器对于分支在3或3一下的switch case 的做法与if else 的做法,大径相同都是采用局部变量与比较值进行比较,依次要比较3次(最坏情况),根据比较结果是否顺序执行指令,或者直接跳转相应指令。
那么,当switch case语句的分支达到3个以上时,编译器还有没有这样做?
我们再加入一个分支,上反汇编
int a=3;
004113EE mov dword ptr [a],3
```
switch(a)
004113F5 mov eax,dword ptr [a]
004113F8 mov dword ptr [ebp-0DCh],eax
004113FE mov ecx,dword ptr [ebp-0DCh]
00411404 sub ecx,1
00411407 mov dword ptr [ebp-0DCh],ecx
0041140D cmp dword ptr [ebp-0DCh],3
00411414 ja $LN2+19h (411487h)
00411416 mov edx,dword ptr [ebp-0DCh]
0041141C jmp dword ptr (4114BCh)[edx*4]
```
{
case 1:
printf("1");
00411423 mov esi,esp
00411425 push offset string "1" (415660h)
0041142A call dword ptr [__imp__printf (4182B8h)]
00411430 add esp,4
00411433 cmp esi,esp
00411435 call @ILT+305(__RTC_CheckEsp) (411136h)
break;
0041143A jmp $LN2+30h (41149Eh)
case 2:
printf("2");
0041143C mov esi,esp
0041143E push offset string "2" (415648h)
00411443 call dword ptr [__imp__printf (4182B8h)]
00411449 add esp,4
0041144C cmp esi,esp
0041144E call @ILT+305(__RTC_CheckEsp) (411136h)
break;
00411453 jmp $LN2+30h (41149Eh)
case 3:
printf("3");
00411455 mov esi,esp
00411457 push offset string "3" (415644h)
0041145C call dword ptr [__imp__printf (4182B8h)]
00411462 add esp,4
00411465 cmp esi,esp
00411467 call @ILT+305(__RTC_CheckEsp) (411136h)
break;
0041146C jmp $LN2+30h (41149Eh)
case 4:
printf("4");
0041146E mov esi,esp
00411470 push offset string "4" (415640h)
00411475 call dword ptr [__imp__printf (4182B8h)]
0041147B add esp,4
0041147E cmp esi,esp
00411480 call @ILT+305(__RTC_CheckEsp) (411136h)
break;
00411485 jmp $LN2+30h (41149Eh)
default:
printf("0");
00411487 mov esi,esp
00411489 push offset string "0" (41563Ch)
0041148E call dword ptr [__imp__printf (4182B8h)]
00411494 add esp,4
00411497 cmp esi,esp
00411499 call @ILT+305(__RTC_CheckEsp) (411136h)
}
很明显我们发现switch下面的指令不一样的了,这里我们来看看这些指令到底干了什么!
int a=3;
004113EE mov dword ptr [a],3
switch(a)
004113F5 mov eax,dword ptr [a]
//将a的值给了eax,eax存放a
004113F8 mov dword ptr [ebp-0DCh],eax
//将eax的值存放入一个局部变量[ebp-0DCh],局部变量值为a
004113FE mov ecx,dword ptr [ebp-0DCh]
//再将局部变量的值给ecx,ecx的值为a
00411404 sub ecx,1
//将ecx的值减1,ecx=a-1
00411407 mov dword ptr [ebp-0DCh],ecx
//将减后ecx的值给刚才的局部变量,局部变量值为a-1
0041140D cmp dword ptr [ebp-0DCh],3
//让局部变量的值与3进行比较
00411414 ja $LN2+19h (411487h)
//如果 局部变量值 > 3 则跳转到输出default
00411416 mov edx,dword ptr [ebp-0DCh]
//将局部变量的值给edx,edx为a-1
0041141C jmp dword ptr (4114BCh)[edx*4]
//跳转到跳转表所指定的地址
额,看完解释可能还是不懂,这个逻辑到底是怎么样的。
这样我们用逻辑性的语言来解释
首先编译器将switch case语句的case后面那个常量做成了一个表,
由于switch case 中只能用整型常量来做判断,所以每一个表中的元素都是4字节,这4个字节里存放的都是对应case的语句的下面执行指令的地址,就是EIP的值。
我的表建立在(4114BCh)这里,通过内存查看到按照顺序来看
0x004114BC 23 14 41 00 #.A.//第一个case下面的指令00411423
0x004114C0 3c 14 41 00 <.A.//第二个case下面的指令0041143c
0x004114C4 55 14 41 00 U.A.//第三个case下面的指令00411455
0x004114C8 6e 14 41 00 n.A.//第四个case下面的指令0041146e
那么表中建立的是指令地址,那么常量是怎么用呢?
我们向switch中给了一个整型变量,这里我们将整型变量的值-1与最大一个case 后面的整型常量值-1,进行比较,如果大于则说明没有case符合跳到default来执行。
那为啥要减1?
0041141C jmp dword ptr (4114BCh)[edx*4]
edx就是偏移量,这样由于我是定义四个case语句,如果不给传入的整型变量-1
那么edx就是3,3*4是12。
第一个case 的指令的偏移量是0
第二个case 的指令的偏移量是4
第三个case 的指令的偏移量是8
第四个case 的指令的偏移量是12
咋一看,没问题,但是符合我们整型变量的case是第三个,这一弄导致出错了,所有我们采用-1的这个方法。
可能有人问了,那我如果是case -1,或者case -2 或者case 后面的值完全乱序,一会正一会负,那又是如何实现!!
如果有人做了,那就会发现我上面总结的都是狗屁不通,强行解释。。。(= ̄ω ̄=)
现在我们来看看乱了后到底是怎么样,当然这些变化主要体现在这几个指令上
sub指令是会变成add还是消失了?
cmp指令后面比较的值到底是根据什么变化的?
跳转表又是怎么样了?怎么弄的?
第一
乱序:当case后只有正数时,没有sub指令也没有add而是消失了
cmp指令后面是case中 正数最大值
jmp指令前增加一个指令
004135DD movzx edx,byte ptr (413698h)[ecx]
首先ecx是我们传入的整型变量值,edx值自然是case对应在表中的偏移量
换句换说,表中的指令地址按照了正数大侠进行排序存放地址。
例如:
case 1
case 12
case 4
case 24
但在表中存放的是 1,4,12,24有序的case 下面指令的地址
第二
乱序 :当case 后面既有负数也有正数时,
sub指令变成了add指令,而加的值就是最小的负数
cmp指令后面的比较值则变成了 最小负数的绝对值+最大正数
jmp指令前还加了这个指令
004135DD movzx edx,byte ptr (413698h)[ecx]
当然这还没完,假设我们case后取很大的int值又会怎样?
第三
乱序:
switch语句放弃了跳转表,开始直接比较。
当然想想还都是,过大还是要放弃的,当然这也有可能是我的分支太少。
更多分支可能会有其他变化!
当然还有 有序但数值上不连续的,这里我不讨论了。。。(= ̄ω ̄=)
第四
事实上,switch case有三种实现机制,分别是
1、直接判断
2、跳转表
3、树型结构
编译器根据需要判断表达式的复杂性并采用相应的的实现方式。
总结
1、switch case 与 if else 在常量比较上 当分支大于3时switch语句效率要高于if else语句
2、当非常量比较时,如表达式的比较,自然是选择if else 结构了!
3、为什么switch 要用int常量,看了跳转表就知道,如果是浮点数那偏移量不就完蛋了,怎么跳??
4、还有使用switch语句时还是要 有序的 尽量从0开始,这样使得效率能更好
5、从0开始自然就没sub那条指令咯!
初学,如有错误,请指出,我会修改!!!(= ̄ω ̄=)