5.3 用if构成的多分支流程(2)
由于编译器可以在编译期间对代码进行优化,当代码中的分支结构形成永远不可抵达的分支语句块时,它永远不会被执行,可以被优化掉而不参与编译处理。向代码清单5-6中插入一句“argc = 0;”,这样argc将被“常量传播”,因此可以在编译期得知,“if(argc < 0)”与“else”这两个分支语句块将永远不可抵达,它们就不会再参与编译。
- void IfElseIf(int argc)
- {
- // 仿造可分支归并代码
- argc = 0;
- // 其他代码与代码清单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)代码表现为:
- void IfElseIf(int argc){
- if (argc > 0){ // 判断函数参数argc是否大于0
- printf("argc > 0"); // 比较成功则执行printf("argc > 0");
- return;
- }
- if (argc == 0){ // 判断函数参数argc是否等于0
- printf("argc == 0");// 比较成功则执行printf("argc == 0");
- return;
- }
- printf("argc <= 0"); // 否则执行printf("argc < 0");
- return;
- }
以上是我们在源码中进行的手工优化,编译器是否会按照我们的意图提升运行效率呢?开启O2编译选项,还原修改过的代码清单5-6,去掉“argc = 0;”再次编译。使用IDA分析反汇编代码,如代码清单5-7所示。
代码清单5-7 优化后的多分支结构—Release版
- ; 函数入口处,对应代码清单5-6中if…else if函数
- .text:00401000 sub_401000 proc near ; CODE XREF: _main+5p
- ; arg_0为函数参数
- .text:00401000 arg_0 = dword ptr 4
- ; 取出参数数据,放入eax,进行第一次if比较
- .text:00401000 mov eax, [esp+arg_0]
- .text:00401004 test eax, eax
- ; 根据比较结果,使用条件跳转指令JLE,若小于等于则跳转到地址标号short loc_401016处
- .text:00401006 jle short loc_401016
- ; 跳转失败,执行printf函数参数传递及调用,显示字符串"argc > 0"
- .text:00401008 push offset Format ; "argc > 0"
- .text:0040100D call _printf
- .text:00401012 add esp, 4
- ; 使用retn指令返回,结束函数调用
- .text:00401015 retn
-
- ; 下面指令的注释是由IDA做的标记
- ; 表示此处代码被标号sub_401000地址加6的地方引用
- .text:00401016 loc_401016: ; CODE XREF: sub_401000+6j
- ; 第二条if比较,由于之前已经使用过test指令进行比较,这里省去重复操作
- ; 直接使用条件跳转指令JNZ,若不等于0则跳转到地址标号short loc_401026处
- .text:00401016 jnz short loc_401026
- ; 跳转失败,执行printf函数参数传递及调用,显示字符串"argc == 0"
- .text:00401018 push offset aArgc0_0 ; "argc == 0"
- .text:0040101D call _printf
- .text:00401022 add esp, 4
- ; 使用retn指令返回,结束函数调用
- .text:00401025 retn
-
- ; 前两次比较判断都失败,执行此处代码
- .text:00401026 loc_401026:; CODE XREF: sub_401000:loc_401016j
- .text:00401026 push offset aArgc0_1 ; "argc <= 0"
- .text:0040102B call _printf
- .text:00401030 pop ecx
- .text:00401031 retn
- .text:00401031 sub_401000 endp
由于选择的是O2优化选项,因此在优化方向上更注重效率,而不是节省空间。既然是对效率的优化,就会尽量减少分支中指令的使用。代码清单5-7中就省去了else对应的JMP指令,当第一次比较成功后,则直接在执行分支语句块后返回,省去了一次跳转操作,从而提升效率。