设为首页 加入收藏

TOP

5.4 switch的真相(3)
2013-10-07 14:30:17 来源: 作者: 【 】 浏览:58
Tags:5.4 switch 真相

5.4 switch的真相(3)

保证了switch的参数值在case最大值的范围内,就可以以地址0x00401198作为基地址进行寻址了,查表①后跳转到对应case地址处。地址0x00401198就是case地址表(数组)的首地址,图5-3便是代码清单5-12的case地址表信息。

 
图5-3 有序线性case地址表

图5-3以0x00401198为起始地址,每4个字节数据保存了一个case语句块的首地址。依次排序下来,第一个case语句块所在地址为0x0040112F。表中第0项保存的内容为0x0040112F,即case 1 语句块的首地址。当输入给switch的参数值为1时,编译器减1调整到case地址数组的下标0后,eax*4+401198h就变成了0 * 4 + 0x00401198,查表得到第0项,即得到case 1语句块的首地址。其他case语句块首地址的查询同理,不再赘述。case语句块的首地址可以对照代码清单5-13查询。

代码清单5-13 线性的case语句块—Debug版

  1. case 1: printf("nIndex == 1");          // 源码对比  
  2. ; 取字符串"nIndex == 1"的首地址0x0004200470作为参数并压栈  
  3. 0040112F  push      offset string "nIndex == 1" (00420070)  
  4. ; 调用printf函数输出字符串,__cdecl调用方式  
  5. 00401134  call      printf (004014b0)  
  6. ; 平衡printf参数的栈空间  
  7. 00401139  add           esp,4  
  8. break;                              // 源码对比  
  9. ; 跳转到switch结束处,以下case语句相似,不做注释说明  
  10. 0040113C  jmp           $L556+0Dh (00401187)  
  11. case 2: printf("nIndex == 2");          // 源码对比  
  12. 0040113E    push        offset string "nIndex == 2" (00420064)  
  13. 00401143    call        printf (004014b0)  
  14. 00401148    add         esp,4  
  15. break;                              // 源码对比  
  16. 0040114B    jmp     $L556+0Dh (00401187)  
  17. case 3: printf("nIndex == 3");          // 源码对比  
  18. 0040114D    push        offset string "nIndex == 3" (00420058)  
  19. 00401152    call        printf (004014b0)  
  20. 00401157    add         esp,4  
  21. break;                              // 源码对比  
  22. 0040115A    jmp         $L556+0Dh (00401187)  
  23. case 5: printf("nIndex == 5");          // 源码对比  
  24. 0040115C    push        offset string "nIndex == 5" (00420048)  
  25. 00401161    call        printf (004014b0)  
  26. 00401166    add         esp,4  
  27. break;                          // 源码对比  
  28. 00401169    jmp         $L556+0Dh (00401187)  
  29. case 6: printf("nIndex == 6");          // 源码对比  
  30. 0040116B    push        offset string "nIndex == 6" (00421024)  
  31. 00401170    call        printf (004014b0)  
  32. 00401175    add         esp,4  
  33. break;                          // 源码对比  
  34. 00401178    jmp         $L556+0Dh (00401187)  
  35. case 7: printf("nIndex == 7");          // 源码对比  
  36. 0040117A    push        offset string "nIndex == 7" (0042003c)  
  37. 0040117F    call        printf (004014b0)  
  38. 00401184    add         esp,4  
  39. break;                              // 源码对比 

将图5-3和代码清单5-13对比可知,每个case语句块的首地址都在表中,但有一个地址却不是case语句块的首地址0x00401187。这个地址是每句break跳转的一个地址值,显然这是switch结束的地址。这个地址值出现在图5-3表格的第3项,表格项的下标以0为起始,反推回case应该是(3+1=4)4,而实际中却没有case 4 这个语句块。为了达到线性有序,对于没有case对应的数值,编译器以switch的结束地址或者default语句块的首地址填充对应的表格项。

代码清单5-13中的每一个break语句都对应一个jmp指令,跳转到的地址都是switch的结束地址处,起到了跳出switch的作用。如果没有break语句,则会顺序执行代码,执行到下一句case语句块中,这便是case语句中没有break可以顺序执行的原因。

代码清单5-13中没有使用default语句块。当所有条件都不成立后,才会执行到default语句块,它和switch的末尾实质上是等价的。switch中出现default后,就会填写default语句块的首地址作为switch的结束地址。

如果每两个case值之间的差值小于等于6,并且case语句数大于等于4,编译器中就会形成这种线性结构。在编写代码的过程中无需有序排列case值,编译器会在编译过程中对case线性地址表进行排序,如case的顺序为3、2、1、4、5,在case线性地址表中,会将它们的语句块的首地址进行排序,将case 1语句块的首地址放在case线性地址表的第0项上,case 2语句块首地址放在表中第1项,以此类推,将首地址变为一个有序的表格进行存放。

这种switch的识别有两个关键点,取数值内容进行比较;比较跳转失败后,出现4字节的相对比例因子寻址方式。有了这两个特性,就可以从代码中正确分析出switch结构。switch结构中的case线性地址模拟图如图5-4所示。

 
图5-4 case线性地址表模拟图

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

评论

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