在查看工作代码的过程中,我发现了一些(看似)令人反感的代码,其中一个函数具有返回类型,但没有返回。我知道代码有效,但认为它只是编译器中的一个错误。
我编写了以下测试并使用我的编译器运行它(gcc(Homebrew gcc 5.2.0)5.2.0)
#include <stdio.h>
int f(int a, int b) {
int c = a + b;
}
int main() {
int x = 5, y = 6;
printf("f(%d,%d) is %d\n", x, y, f(x,y)); // f(5,6) is 11
return 0;
}
类似于我在工作中找到的代码,这默认返回函数中执行的最后一个表达式的结果。
我发现this问题,但对答案不满意。我知道使用-Wall -Werror
可以避免这种行为,但为什么它是一个选项呢?为什么仍然允许?
答案 0 :(得分:12)
“为什么这仍然允许?”它不是,但一般来说,编译器不能证明你正在做它。考虑这个(当然非常简化)的例子:
// Input is always true because logic reason
int fun (bool b) {
if (b) {
return 7;
}
}
甚至是这个:
int fun (bool b) {
if (b) {
return 7;
}
// Defined in a different translation unit, will always call exit()
foo();
// Now we can never get here, but the compiler cannot know
}
现在第一个例子可以流出结尾,但只要函数“正确”使用它就永远不会;而第二个不能,但编译器不能知道这一点。所以编译器会通过使这个错误打破“工作”和合法,尽管可能是愚蠢的代码。
现在您发布的示例略有不同:此处,所有路径都流出结束,因此编译器可以拒绝或忽略此功能。然而,它会破坏依赖于生产代码中的编译器特定行为的真实世界代码,并且人们不喜欢它,即使它们是错误的。
但最终,流出非void
函数的结尾仍然是未定义的行为。它可能适用于某些编译器,但它不是,也绝不保证。
答案 1 :(得分:4)
具有返回类型但没有返回语句的函数的结果是未定义的(除了在带有main
的C ++中,返回值为0)。它也不是语法错误,因为语法使用一般函数表示一个适用于值返回和void函数的函数。
这里发生的事情是,最后一个表达式的结果存储在编译器用于函数返回值的同一寄存器(在x86,EAX或RAX上)中。当函数返回时,返回指令单独留下该寄存器,并且调用代码只是假设该值具有该函数的返回值。
答案 2 :(得分:4)
这不是编译器中的错误,但代码显然很糟糕。在x86架构上,(R | E)AX用作累加器寄存器,也用作返回值。
您会看到f()
的代码确实使用了eax来存储添加结果。
然而 ...继续使用-O1
(或任何更积极的优化级别)作为编译器选项(右上方框),看看现在发生了什么
...我们发现编译器已经正确地意识到函数没有明确地返回它的结果,它只是变成了无操作。
因此,这段令人讨厌的代码是一个完美的例子,它可以像调试版本一样工作,但在应用任何优化时都会失败。
答案 3 :(得分:1)
在计算机体系结构级别:如果内存中存在默认位置,在简单计算后存储结果,则可以使用正确答案返回(无意中)此内存。
这是:
至少对于x86,此函数的返回值应该在eax寄存器中。那里的任何东西都会被调用者视为返回值。
因为eax用作返回寄存器,所以它通常用作&#34; scratch&#34;由calee注册,因为它不需要保留。这意味着它很可能被用作任何局部变量。因为它们两者在最后是相等的,所以更有可能将正确的值保留在eax中。
检查here类似主题。