设为首页 加入收藏

TOP

5.4 switch的真相(2)
2013-10-07 14:30:15 来源: 作者: 【 】 浏览:55
Tags:5.4 switch 真相

5.4 switch的真相(2)

将代码清单5-10与代码清单5-9进行对比分析:if…else if结构会在条件跳转后紧跟语句块;而switch结构则将所有的条件跳转都放置在了一起,并没有发现case语句块的踪影。通过条件跳转指令,跳转到相应case语句块中,因此每个case的执行是由switch比较结果引导“跳”过来的。所有case语句块都是连在一起的,这样是为了实现C语法的要求,在case语句块中没有break语句时,可以顺序执行后续case语句块。

总结:

  1. mov     reg, mem            ; 取出switch中考察的变量  
  2. ; 影响标志位的指令   
  3. jxx    xxxx             ; 跳转到对应case语句块的首地址处  
  4. ; 影响标志位的指令   
  5. jxx    xxxx   
  6. ; 影响标志位的指令  
  7. jxx    xxxx   
  8. jmp     END             ; 跳转到switch的结尾地址处  
  9. ......                  ; case语句块的首地址  
  10. jmp     END             ; case语句块结束,有break则产生这个jmp  
  11. ......                  ; case语句块的首地址  
  12. jmp     END             ; case语句块的结束,有break则产生这个jmp  
  13. ......                  ; case语句块的首地址  
  14. jmp     END             ; case语句块结束,有break则产生这个jmp  
  15. END:                    ; switch结尾  
  16. ...... 

遇到这样的代码块,需要重点考察每个条件跳转指令后是否跟有语句块,以辨别switch分支结构。根据每个条件跳转到的地址来分辨case语句块首地址。如果case语句块内有break,会出现jmp作为结尾。如果没有break,可参考两个条件跳转所跳转到的目标地址,这两个地址之间的代码便是一个case语句块。

在switch分支数小于4的情况下,VC++(www.cppentry.com) 6.0采用模拟if…else if的方法。这样做并没有发挥出switch的优势,在效率上也没有if…else if强。当分支数大于3,并且case的判定值存在明显线性关系组合时,switch的优化特性便可以凸显出来,如代码清单5-11所示。

代码清单5-11 有序线性的C++(www.cppentry.com)示例代码

  1. int nIndex = 0;  
  2. scanf("%d", & nIndex);  
  3. switch(nIndex){  
  4. case 1: printf("nIndex == 1");break;  
  5. case 2: printf("nIndex == 2");break;  
  6. case 3: printf("nIndex == 3");break;  
  7. case 5: printf("nIndex == 5");break;  
  8. case 6: printf("nIndex == 6");break;  
  9. case 7: printf("nIndex == 7");break;  

在此段代码中,case语句的标号为一个数值为1~7的有序序列。按照if…else if转换规则,会将1~7的数值依次比较一次,从而得到分支选择结果。这么做需要比较的次数太多,如何降低比较次数,提升效率呢?由于是有序线性的数值,可将每个case语句块的地址预先保存在数组中,考察switch语句的参数,并依此查询case语句块地址的数组,从而得到对应case语句块的首地址,通过代码清单5-12,验证这一优化方案。

代码清单5-12 有序线性示例—Debug版

  1. switch(nIndex) {                // 源码对比  
  2. ; 将变量nIndex内容放入ecx中  
  3. 00401110   mov      ecx,dword ptr [ebp-4]  
  4. ; 取出ecx的值并放入临时变量ebp-8中  
  5. 00401113   mov      dword ptr [ebp-8],ecx  
  6. ; 取临时变量的值放入edx中,这几句代码的功能看似没有区别  
  7. ; 只有在Debug版下才会出现  
  8. 00401116   mov      edx,dword ptr [ebp-8]  
  9. ; 对edx减1,进行下标平衡  
  10. 00401119   sub      edx,1  
  11. ; 将加1后的临时变量放回  
  12. 0040111C   mov      dword ptr [ebp-8],edx  
  13. ; 判断临时变量是否大于6  
  14. 0040111F   cmp      dword ptr [ebp-8],6  
  15. ; 大于6跳转到0x00401187处  
  16. 00401123   ja       $L556+0Dh (00401187)  
  17. ; 取出临时变量的值放到eax中  
  18. 00401125   mov      eax,dword ptr [ebp-8]  
  19. ; 以eax为下标,0x00401198为基址进行寻址,跳转到该地址处  
  20. ; 注意:地址0x00401198就是case地址数组  
  21. 00401128   jmp      dword ptr [eax*4+401198h] 

代码清单5-12的第4条汇编语句为什么要对edx减1呢?因为代码中为case语句制作了一份case地址数组(或者称为“case地址表”),这个数组保存了每个case语句块的首地址,并且数组下标是以0为起始。而case中的最小值是1,与case地址表的起始下标是不对应的,所以需要对edx减1调整,使其可以作为表格的下标进行寻址。

在进入switch后会先进行一次比较,检查输入的数值是否大于case值的最大值,由于case的最小值为1,那么对齐到0下标后,示例中case的最大值为(7-1=6)6。又由于使用了无符号比较(ja指令是无符号比较,大于则跳转),当输入的数值为0或一个负数时,同样会大于6,将直接跳转到switch的末尾。当然,如果有default分支,就直接跳至default语句块的首地址。当case的最小值为0时,不需要调整下标,当然也不会出现类似“sub edx,1”这样的下标调整代码。

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇5.4 switch的真相(3) 下一篇5.4 switch的真相(1)

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: