设为首页 加入收藏

TOP

5.3 用if构成的多分支流程(2)
2013-10-07 14:30:31 来源: 作者: 【 】 浏览:58
Tags:5.3 构成 分支 流程

5.3 用if构成的多分支流程(2)

由于编译器可以在编译期间对代码进行优化,当代码中的分支结构形成永远不可抵达的分支语句块时,它永远不会被执行,可以被优化掉而不参与编译处理。向代码清单5-6中插入一句“argc = 0;”,这样argc将被“常量传播”,因此可以在编译期得知,“if(argc < 0)”与“else”这两个分支语句块将永远不可抵达,它们就不会再参与编译。

  1. void IfElseIf(int argc)  
  2. {  
  3.         // 仿造可分支归并代码  
  4.         argc = 0;  
  5.         // 其他代码与代码清单5-6相同  

选择O2编译选项,将修改后的代码再次编译。使用IDA查看优化后的不可达分支是否被删除,如图5-2所示。
 
图5-2 优化后的不可达分支结构

优化后,图5-2中的不可达分支被删除了。由于只剩下一个必达的分支语句块,编译器直接提取出必达分支语句块中的代码,将整个分支结构替换,就形成了如图5-2所示的代码。更多分支结构的优化,会遵循第4章中讲述的各种优化方案。以代码清单5-6为例,此多分支结构执行结束后,并没有做任何工作,直接函数返回;且当某一分支判断成立时,其他分支将不会被执行。可以选择在每个语句块内插入return语句,以减少跳转次数。

代码清单5-6中的多分支结构,共有两条比较语句块。如果其中一个分支成立,则其他分支结构语句块便会被跳过。因此可将前两个分支语句块转换为单分支if结构,在各分支语句块中插入return语句,这样既没有破坏程序流程,又可以省略掉else语句。由于没有了else,减少了一次JMP跳转,使程序执行效率得到提高。其C++(www.cppentry.com)代码表现为:

  1. void IfElseIf(int argc){  
  2.       if (argc > 0){            // 判断函数参数argc是否大于0  
  3.         printf("argc > 0"); // 比较成功则执行printf("argc > 0");  
  4.             return;   
  5.       }  
  6.       if (argc == 0){       // 判断函数参数argc是否等于0  
  7.             printf("argc == 0");// 比较成功则执行printf("argc == 0");  
  8.             return;  
  9.       }  
  10.       printf("argc <= 0");  // 否则执行printf("argc < 0");  
  11.       return;  

以上是我们在源码中进行的手工优化,编译器是否会按照我们的意图提升运行效率呢?开启O2编译选项,还原修改过的代码清单5-6,去掉“argc = 0;”再次编译。使用IDA分析反汇编代码,如代码清单5-7所示。

代码清单5-7 优化后的多分支结构—Release版

  1. ; 函数入口处,对应代码清单5-6中if…else if函数  
  2. .text:00401000 sub_401000      proc near  ; CODE XREF: _main+5p  
  3. ; arg_0为函数参数  
  4. .text:00401000 arg_0           = dword ptr  4  
  5. ; 取出参数数据,放入eax,进行第一次if比较  
  6. .text:00401000  mov     eax, [esp+arg_0]  
  7. .text:00401004  test      eax, eax  
  8. ; 根据比较结果,使用条件跳转指令JLE,若小于等于则跳转到地址标号short loc_401016处  
  9. .text:00401006  jle       short loc_401016  
  10. ; 跳转失败,执行printf函数参数传递及调用,显示字符串"argc > 0"   
  11. .text:00401008  push     offset Format   ; "argc > 0"  
  12. .text:0040100D  call      _printf  
  13. .text:00401012  add      esp, 4  
  14. ; 使用retn指令返回,结束函数调用  
  15. .text:00401015  retn  
  16.  
  17. ; 下面指令的注释是由IDA做的标记  
  18. ; 表示此处代码被标号sub_401000地址加6的地方引用  
  19. .text:00401016 loc_401016:      ; CODE XREF: sub_401000+6j  
  20. ; 第二条if比较,由于之前已经使用过test指令进行比较,这里省去重复操作  
  21. ; 直接使用条件跳转指令JNZ,若不等于0则跳转到地址标号short loc_401026处  
  22. .text:00401016  jnz     short loc_401026  
  23. ; 跳转失败,执行printf函数参数传递及调用,显示字符串"argc == 0"  
  24. .text:00401018  push    offset aArgc0_0 ; "argc == 0"   
  25. .text:0040101D  call    _printf  
  26. .text:00401022  add     esp, 4  
  27. ; 使用retn指令返回,结束函数调用  
  28. .text:00401025  retn  
  29.  
  30. ; 前两次比较判断都失败,执行此处代码  
  31. .text:00401026 loc_401026:; CODE XREF: sub_401000:loc_401016j  
  32. .text:00401026  push    offset aArgc0_1 ; "argc <= 0"  
  33. .text:0040102B  call    _printf  
  34. .text:00401030  pop     ecx  
  35. .text:00401031  retn  
  36. .text:00401031 sub_401000      endp 

由于选择的是O2优化选项,因此在优化方向上更注重效率,而不是节省空间。既然是对效率的优化,就会尽量减少分支中指令的使用。代码清单5-7中就省去了else对应的JMP指令,当第一次比较成功后,则直接在执行分支语句块后返回,省去了一次跳转操作,从而提升效率。
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇12.2 多重继承(1) 下一篇5.3 用if构成的多分支流程(1)

评论

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