使用级联“else if”语句有什么缺点吗?

时间:2012-07-29 18:52:49

标签: c if-statement curly-brackets

这个问题几乎与所有类似C语言的“大括号”编程语言有关。

我说的是使用以下内容:

if(condition)
    meh();
else if(condition1)
    bleh();
else if(condition2)
    moo();
else
    foo();

在代码中使用这个成语时是否有任何需要注意的注意事项?我正在寻找性能惩罚,编译器限制等等。典型的编译器会用这样的东西做什么?

我问,因为即使它对人眼看起来很好也很平坦,实际上它会被严格解析为等同于以下内容,并添加了括号:

if(condition)
{
     meh();
}
else
{
    if(condition1)
    {
        bleh();
    }
    else
    {
        //...
    }
}

即。 else if并不是真正的分隔符;相反,每个if将嵌套在前面的else中。这就像解析x + y + z + ...为x +(y +(z + ...))。

编译器是否真的以这种方式对待它,还是会将else if视为特例?如果是前者,我必须注意哪些警告?

(这是我在StackOverflow上的第一个问题。)

4 个答案:

答案 0 :(得分:2)

当我看到这个。就优化而言,我认为这是我的想法;

if(blah)
  //Most likely code to be run.
elseif(bleh)
  //Less likely code to be run.
elseif(blarg)
  //Even less likely to be run.
else
  //Almost never ever gets here.

根据您的评估结果,switch语句可能会更好。

答案 1 :(得分:1)

就语言而言,这两种结构完全相同。编译器可以自由地创建任何类型的机器代码来创建强制行为,并且极有可能优化编译器将以完全相同的方式处理这两个版本。

如果需要,编译器甚至可以用跳转表替换重复的条件。同样,无论您的代码是否使用此类实现,都应该无关紧要。 (如果你真的好奇,只需编译并比较装配。)

以一种使其结构最明显和最清晰的方式编写代码。如果所有分支都处于平等地位,我个人更喜欢else if s,甚至是switch语句。

答案 2 :(得分:1)

尽可能将变体与else-if一起使用。这种结构没有缺点。

确实无法保证有关编译器优化的任何内容。同时经验和合理的思考都表明,这种复杂性的构造对于现代编译器来说不是问题。

答案 3 :(得分:1)

我正在使用以下简单代码进行一些实验,以了解编译器在优化if-else结构时所执行的操作。我正在使用的代码是

#include <stdio.h>

int main() {
        int arr[] = {1,2,3,4,5,6,7};
        int i;
        for(i = 0; i < 5; i++) {
                if(arr[i] == 1)
                        printf("one\n");
                else if (arr[i] == 2)
                        printf("two\n");
                else if (arr[i] = 3)
                        printf("three\n");
                else printf("blah\n");
        }
        return 0;
}

当然不是很好的例子。由于此处没有任何动态来区分可能的不太可能的早午餐。

但令我惊讶的是,它产生的代码却截然不同。

首先没有任何优化我有:

   0x0000000000400506 <+66>:    mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000400509 <+69>:    cdqe   
   0x000000000040050b <+71>:    mov    eax,DWORD PTR [rbp+rax*4-0x20]
   0x000000000040050f <+75>:    cmp    eax,0x1
   0x0000000000400512 <+78>:    jne    0x400520 <main+92>
   0x0000000000400514 <+80>:    mov    edi,0x400668
   0x0000000000400519 <+85>:    call   0x4003b8 <puts@plt>
   0x000000000040051e <+90>:    jmp    0x400551 <main+141>
   0x0000000000400520 <+92>:    mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000400523 <+95>:    cdqe   
   0x0000000000400525 <+97>:    mov    eax,DWORD PTR [rbp+rax*4-0x20]
   0x0000000000400529 <+101>:   cmp    eax,0x2
   0x000000000040052c <+104>:   jne    0x40053a <main+118>
   0x000000000040052e <+106>:   mov    edi,0x40066c
   0x0000000000400533 <+111>:   call   0x4003b8 <puts@plt>
   0x0000000000400538 <+116>:   jmp    0x400551 <main+141>
   0x000000000040053a <+118>:   mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040053d <+121>:   cdqe   
   0x000000000040053f <+123>:   mov    DWORD PTR [rbp+rax*4-0x20],0x3
   0x0000000000400547 <+131>:   mov    edi,0x400670
   0x000000000040054c <+136>:   call   0x4003b8 <puts@plt>

代码很简单。顺序cmpjne是预期的if-else结构的核心。

但乐趣始于(-O3

0x0000000000400510 <+64>:   call   0x4003b8 <puts@plt>
   0x0000000000400515 <+69>:    mov    eax,DWORD PTR [rsp+0x4]
   0x0000000000400519 <+73>:    cmp    eax,0x1
   0x000000000040051c <+76>:    je     0x400640 <main+368>
   0x0000000000400522 <+82>:    cmp    eax,0x2
   0x0000000000400525 <+85>:    je     0x4005a0 <main+208>
   0x0000000000400527 <+87>:    mov    edi,0x40074c
   0x000000000040052c <+92>:    mov    DWORD PTR [rsp+0x4],0x3
   0x0000000000400534 <+100>:   call   0x4003b8 <puts@plt>
   0x0000000000400539 <+105>:   mov    eax,DWORD PTR [rsp+0x8]
   0x000000000040053d <+109>:   cmp    eax,0x1
   0x0000000000400540 <+112>:   je     0x4005b3 <main+227>
   0x0000000000400542 <+114>:   cmp    eax,0x2
   0x0000000000400545 <+117>:   je     0x400630 <main+352>
   0x000000000040054b <+123>:   mov    edi,0x40074c
   0x0000000000400550 <+128>:   mov    DWORD PTR [rsp+0x8],0x3
   0x0000000000400558 <+136>:   call   0x4003b8 <puts@plt>
   0x000000000040055d <+141>:   mov    eax,DWORD PTR [rsp+0xc]
   0x0000000000400561 <+145>:   cmp    eax,0x1
   0x0000000000400564 <+148>:   je     0x4005d0 <main+256>
   0x0000000000400566 <+150>:   cmp    eax,0x2
   0x0000000000400569 <+153>:   je     0x400618 <main+328>
   0x000000000040056f <+159>:   mov    edi,0x40074c
   0x0000000000400574 <+164>:   mov    DWORD PTR [rsp+0xc],0x3
   0x000000000040057c <+172>:   call   0x4003b8 <puts@plt>
   0x0000000000400581 <+177>:   mov    eax,DWORD PTR [rsp+0x10]
   0x0000000000400585 <+181>:   cmp    eax,0x1
   0x0000000000400588 <+184>:   je     0x4005e8 <main+280>
   0x000000000040058a <+186>:   cmp    eax,0x2
   0x000000000040058d <+189>:   je     0x400600 <main+304>
   0x000000000040058f <+191>:   mov    edi,0x40074c
   0x0000000000400594 <+196>:   call   0x4003b8 <puts@plt>
   0x0000000000400599 <+201>:   xor    eax,eax
   0x000000000040059b <+203>:   add    rsp,0x28
   0x000000000040059f <+207>:   ret    
   0x00000000004005a0 <+208>:   mov    edi,0x400752
   0x00000000004005a5 <+213>:   call   0x4003b8 <puts@plt>
   0x00000000004005aa <+218>:   mov    eax,DWORD PTR [rsp+0x8]
   0x00000000004005ae <+222>:   cmp    eax,0x1
   0x00000000004005b1 <+225>:   jne    0x400542 <main+114>
   0x00000000004005b3 <+227>:   mov    edi,0x400748
   0x00000000004005b8 <+232>:   call   0x4003b8 <puts@plt>
   0x00000000004005bd <+237>:   mov    eax,DWORD PTR [rsp+0xc]
   0x00000000004005c1 <+241>:   cmp    eax,0x1
   0x00000000004005c4 <+244>:   jne    0x400566 <main+150>
   0x00000000004005c6 <+246>:   nop    WORD PTR cs:[rax+rax*1+0x0]
   0x00000000004005d0 <+256>:   mov    edi,0x400748
   0x00000000004005d5 <+261>:   call   0x4003b8 <puts@plt>
   0x00000000004005da <+266>:   mov    eax,DWORD PTR [rsp+0x10]
   0x00000000004005de <+270>:   cmp    eax,0x1
   0x00000000004005e1 <+273>:   jne    0x40058a <main+186>
   0x00000000004005e3 <+275>:   nop    DWORD PTR [rax+rax*1+0x0]
   0x00000000004005e8 <+280>:   mov    edi,0x400748
   0x00000000004005ed <+285>:   call   0x4003b8 <puts@plt>
   0x00000000004005f2 <+290>:   xor    eax,eax
   0x00000000004005f4 <+292>:   add    rsp,0x28
   0x00000000004005f8 <+296>:   ret    
   0x00000000004005f9 <+297>:   nop    DWORD PTR [rax+0x0]
   0x0000000000400600 <+304>:   mov    edi,0x400752
   0x0000000000400605 <+309>:   call   0x4003b8 <puts@plt>
   0x000000000040060a <+314>:   xor    eax,eax
   0x000000000040060c <+316>:   add    rsp,0x28
   0x0000000000400610 <+320>:   ret    
   0x0000000000400611 <+321>:   nop    DWORD PTR [rax+0x0]
   0x0000000000400618 <+328>:   mov    edi,0x400752
   0x000000000040061d <+333>:   call   0x4003b8 <puts@plt>
   0x0000000000400622 <+338>:   jmp    0x400581 <main+177>
   0x0000000000400627 <+343>:   nop    WORD PTR [rax+rax*1+0x0]
   0x0000000000400630 <+352>:   mov    edi,0x400752
   0x0000000000400635 <+357>:   call   0x4003b8 <puts@plt>
   0x000000000040063a <+362>:   jmp    0x40055d <main+141>
   0x000000000040063f <+367>:   nop
   0x0000000000400640 <+368>:   mov    edi,0x400748
   0x0000000000400645 <+373>:   call   0x4003b8 <puts@plt>
   0x000000000040064a <+378>:   jmp    0x400539 <main+105>

这里需要注意的重要事项:

  1. 很多无条件的跳转来移动代码。

  2. 使用je代替jne

  3. 有许多重复的代码区域。与1的比较已多次进行。

  4. 我将深入研究优化的汇编程序,并将此帖子更新为任何有趣的查找。这不是答案,而是调查并邀请其他人进行类似的调查,以找出重要的优化实践。

    编辑:

    编译器信息:

    [root@s1 ~]# gcc --version
    gcc (GCC) 4.4.6 20110731 (Red Hat 4.4.6-3)
    

    优化信息:

    -O2打开以下优化标志:

    -fthread-jumps 
      -falign-functions  -falign-jumps 
      -falign-loops  -falign-labels 
      -fcaller-saves 
      -fcrossjumping 
      -fcse-follow-jumps  -fcse-skip-blocks 
      -fdelete-null-pointer-checks 
      -fdevirtualize 
      -fexpensive-optimizations 
      -fgcse  -fgcse-lm  
      -fhoist-adjacent-loads 
      -finline-small-functions 
      -findirect-inlining 
      -fipa-sra 
      -foptimize-sibling-calls 
      -fpartial-inlining 
      -fpeephole2 
      -fregmove 
      -freorder-blocks  -freorder-functions 
      -frerun-cse-after-loop  
      -fsched-interblock  -fsched-spec 
      -fschedule-insns  -fschedule-insns2 
      -fstrict-aliasing -fstrict-overflow 
      -ftree-switch-conversion -ftree-tail-merge 
      -ftree-pre 
      -ftree-vrp
    

    -O3将使用-O2添加额外的优化:

    -finline-functions, -funswitch-loops, -fpredictive-commoning, -fgcse-after-reload, -ftree-vectorize, -fvect-cost-model, -ftree-partial-pre and -fipa-cp-clone
    

    if-else阻止相关优化:

      

    -fcse-后续跳跃       在公共子表达式消除(CSE)中,当任何其他目标未达到跳转目标时,扫描跳转指令   路径。例如,当CSE遇到带有else的if语句时   当条件测试为假时,CSE跟随跳转。

         

    -fcse跳过块       这与-fcse-follow-jumps类似,但会导致CSE跟踪有条件地跳过块的跳转。当CSE遇到一个   简单if if语句没有else子句,-fcse-skip-blocks导致CSE   跟随if的身体跳跃。

         

    fhoist相邻-负载       如果负载来自相同结构中的相邻位置,则推测性地从if-then-else的两个分支中提升负载   目标体系结构具有条件移动指令。这个标志是   默认情况下启用-O2和更高版本。