在if内部返回还是在else外面返回?(这是一个关于IT技术的问题)

3

(C)如果我有一个包含if的函数,如果条件为真,则可以返回某个值,否则返回另一个值。使用else更高效还是不使用更好?

例如...

int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    } else {
        return 1;
    }
    return 0;
}

或者只是
int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    }
    return 1;
}

假设使用GCC编译,第一个实现结果的编译代码与第二个有何不同?

我需要尽可能高效,因此在else中减少分支是很好的 - 但从风格上讲,我的内在强迫症不喜欢看到返回的不是0或void作为函数的最后一条指令,因为这样不太清楚正在发生什么。所以如果它将被排除在外,那么我可以保留else ...


1
尝试两种方式。比赛你的马。 - Bathsheba
1
在第一次实现中,由于ifelse都有一个return语句,那个最终的return语句将永远不会被执行到。 - Drew McGowen
bool c = 0 < a && a < SOME_LIMIT; if (c) { b = a; } return c; 是我最喜欢的代码片段。 - Joop Eggen
2
可以用一行代码实现:return ((a > 0) && (a < SOME_LIMIT)) ? b = a, 0 : 1; 但即使它能工作,也不是很清晰。 - Kninnug
1
如果你的编译器没有做这个优化,那么if ((unsigned)(a-1) < SOME_LIMIT-1) ...是一个很好的微观优化。但正如其他人所说,每个编译器都不同,因此如果你真的关心的话,最好查看汇编输出和/或进行性能分析。 - Nemo
6个回答

8
您可以使用-O3 -S选项运行gcc以生成优化的汇编代码,这样您就可以看到(并比较)优化后的汇编代码。我对您的源代码进行了以下更改,以使其编译通过。
文件a.c:
int b;                                                                         

int foo (int a) {             
    if ((a > 0) && (a < 5000)) {  
        b = a;                                                    
        return 0;                                                        
    } else {                                                                   
        return 1;             
    }                                       
    return 0;                          
}

文件 b.c:

int b;                                                                         
int foo (int a) {                                                             
    if ((a > 0) && (a < 5000)) {
        b = a;
        return 0;
    }       
    return 1;                                                                  
}

使用gcc -O3 -S a.c编译a.c会创建文件a.s。在我的机器上,它的外观如下:

               .file      "a.c"
               .text
               .p2align 4,,15
               .globl     foo
               .type      foo, @function
foo:
.LFB0:
               .cfi_startproc
               movl       4(%esp), %edx
               movl       $1, %eax
               leal       -1(%edx), %ecx
               cmpl       $4998, %ecx
               ja         .L2
               movl       %edx, b
               xorb       %al, %al
.L2:
               rep
               ret
               .cfi_endproc
.LFE0:
               .size      foo, .-foo
               .comm      b,4,4
               .ident     "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
               .section   .note.GNU-stack,"",@progbits

在使用 gcc -O3 -S b.c 编译 b.c 后,会生成文件 b.s。在我的机器上,它看起来是这样的:
               .file      "b.c"
               .text
               .p2align 4,,15
               .globl     foo
               .type      foo, @function
foo:
.LFB0:
               .cfi_startproc
               movl       4(%esp), %edx
               movl       $1, %eax
               leal       -1(%edx), %ecx
               cmpl       $4998, %ecx
               ja         .L2
               movl       %edx, b
               xorb       %al, %al
.L2:
               rep
               ret
               .cfi_endproc
.LFE0:
               .size      foo, .-foo
               .comm      b,4,4
               .ident     "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
               .section   .note.GNU-stack,"",@progbits

请注意,foo: 的汇编实现是相同的。因此,在这种情况下,使用这个版本的GCC编写代码的方式并不重要。

1
哇,谢谢!我对编译器选项不是很了解(大多数时间都在IDE中工作),所以这让我非常感激,因为如果要自己想办法解决这个问题,可能需要好几个小时的时间。真希望我可以多次点赞! - Toby

1

请检查两个实现之间的目标文件。添加类似于汇编头的内容,例如

 PortionInQuestion: 

那么,这将在您的汇编文件中显示为标签,您可以看到生成的汇编代码有何不同。它们可能完全相同(由于优化),也可能完全不同。如果没有看到原始汇编代码,就无法知道编译器如何进行优化。

1
我会这样写...
int foo (int a) {
    if ((a > 0) && (a < SOME_LIMIT)) {
        b = a //maybe b is some global or something
        return 0;
    } else {
        return 1;
    }
}

整个函数只是一个布尔值,用于将b的值限制在大于零且小于某个常量之间。if语句的条件控制了返回值。函数不需要添加默认返回值,因为默认返回值会使if条件失效。

1
int foo (int a) {

     /* Nothing to do: get out of here */
    if (a <= 0 || a >= SOME_LIMIT) return 1;

    b = a; // maybe b is some global or something

    return 0;
}

就效率而言,几乎没有任何区别(最昂贵的部分是函数调用加上返回)。

对于人类读者来说,最少“缩进”的代码(如上所述)最容易阅读和理解。

顺便说一下,生成的汇编代码对我来说也非常简洁,并且与if (a >0 && a < LIMIT)形式完全等效。

        .file   "return.c"
        .text
        .p2align 4,,15
        .globl  foo
        .type   foo, @function
foo:
.LFB0:
        .cfi_startproc
        leal    -1(%rdi), %edx
        movl    $1, %eax
        cmpl    $2998, %edx
        ja      .L2
        movl    %edi, b(%rip)
        xorb    %al, %al
.L2:
        rep
        ret
        .cfi_endproc
.LFE0:
        .size   foo, .-foo
        .globl  b
        .bss
        .align 16
        .type   b, @object
        .size   b, 4
b:
        .zero   4
        .ident  "GCC: (Ubuntu/Linaro 4.6.1-9ubuntu3) 4.6.1"
        .section        .note.GNU-stack,"",@progbits

1
在第一个示例中,你不会执行到最后的return 0;。因此我认为你的第二个示例至少在风格上更加清晰,因为它更简洁。通常来说,同样的功能用更少的代码实现是好事。
关于性能方面,如果你感兴趣,可以查看汇编可执行文件或对代码进行分析以查看是否存在实际差异。我打赌这没有什么实际影响。
最后,如果你的编译器支持优化标志,请使用它们!

1

当然这取决于编译器。我猜市场上每个(好的)编译器在这两种情况下都会产生完全相同的输出。然而,任何一本关于优化的书都不鼓励尝试这样的微观优化!

选择更易读的形式。


不知道这是不被鼓励的!每次都学到新东西 ;) - Toby
1
“不要过早优化”是一个词,还有“不要过早压缩性能”!(我没有编造这些,你可以在《C++编码标准》、《高效C++》等书中找到这些确切的表达方式) - Stefano Falasca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接