没有返回语句时为什么编译器没有报错?

20

与Java不同,C/C++中以下内容是允许的

int* foo ()
{
  if(x)
    return p;

  // What if control reaches here?
}

这经常导致崩溃,并且很难调试问题。为什么标准不强制要求非void函数有一个最终返回值? (编译器会为错误的return值生成一个错误。)

在GCC或MSVC中是否有一个标志可以强制执行此操作?(类似于-Wunused-result


以下的要点是:如果你想进行这些类型的检查,请在gcc上使用“-Wall”,在Visual Studio中使用高警告级别(3或4)。 - T.J. Crowder
4
我早就想要一个编译器标记,使得所有函数在结尾处都有 throw "LOL Y U HERE??"。由于该行为未定义,因此任何事情都可能发生,抛出异常显然符合“任何事情”。这只是为了测试目的;如果它们在发布版本中被优化掉,我不介意。 - Dennis Zickefoose
如果x被声明为const bool x = true;,那么它不是未定义的。 - Bo Persson
@Dennis:如果我没记错的话,在函数结尾处运行不会导致未定义行为,除非调用者试图使用返回值。 - R.. GitHub STOP HELPING ICE
@R..: 我相信在C语言中是这样的,但是由于构建大多数对象需要更多的工作,所以C++在这方面更加“严格”。即使在C语言中,如果开发人员忘记从可访问控制路径返回某些内容,我想大多数开发人员也会认为这是一个bug。 - Dennis Zickefoose
2
不良的编程风格和未定义行为是不同的。 - R.. GitHub STOP HELPING ICE
7个回答

14

这是不被允许的(未定义行为)。但是,标准在这种情况下不要求诊断。

由于存在以下代码,标准并不要求最后一个语句为return

while (true) {
  if (condition) return 0;
}

这段代码总是返回0,但是一个愚蠢的编译器没能发现它。请注意,标准并不要求智能编译器。在while块后加上return语句是浪费的,而愚蠢的编译器无法优化掉它。标准不想要求程序员编写无用代码,只是为了满足一个愚蠢的编译器。

在我的机器上,g++ -Wall 已经足够聪明了,会发出一条诊断信息。


4
注意,该标准不强制要求使用智能编译器。 - atzol
我认为一个更好的例子可能是这样的:int test(...) { if (...) { [计算某些东西并返回]; } else { log_something(...); FATAL_ERROR(); }; }。如果FATAL_ERROR()无法返回,则test()的结尾永远无法到达,但通常编译器无法知道这一点。 - supercat
如果条件为假(没有前进),则此代码会导致未定义的行为。 - M.M
@M.M 这不是字面上的代码。condition代表任何表达式。 - n. m.

13

GCC中使用-Wall标志。

警告:控制达到非void函数的末尾

更具体地说,使用-Wreturn-type标志。


@T.J. Crowder:只有当文件是 c 文件时才会发生。 - Prince John Wesley
@John:不,对我来说,使用C++也可以做到,只要我没有使用主函数。 - T.J. Crowder
@John:我不知道为什么。 :-) - T.J. Crowder
3
main()函数不需要显式包含return语句,可参考Bjarne Stroustrup的网站http://www2.research.att.com/~bs/bs_faq2.html#void-main。 - iammilind
1
这是由标准定义的,在此处查看Shafik的答案。 - r0n9
显示剩余10条评论

3
我的猜测是:因为有时候程序员比编译器更了解情况。通过这个简单的例子,很明显出现了问题,但是考虑到许多值的开关或一般的检查,你作为编码者,知道某些值不会传递到函数中,但编译器并不知道,只是提示你可能出现了一些问题。
#include <iostream>

int foo(){
    if(false)
        return 5;
}

int main(){
    int i = foo();
    std::cout << i;
}

请注意,MSVC上的甚至警告级别1会给出以下警告:

警告 C4715:'foo':并非所有控制路径都返回值


2
请注意,即使在 MSVC 上警告级别为 1,也会出现以下警告:这很有趣。我在使用 VS 2005 的时候甚至没有收到 4 级的警告。 - T.J. Crowder
@T.J.:根据MSDN的说法,2005年版本也应该在级别1上发出警告。请参见我添加的链接。 - Xeo
@Xeo:有趣。如果我像你一样添加一个foo函数,就会出现错误,但是如果我在自动生成的_tmain函数中完全做同样的事情,则不会出现错误;示例。主函数是特殊的(在很多方面都是这样)。 - T.J. Crowder
这样的控制流警告通常只在优化器运行和分析时发出,因此它们通常仅适用于零售版本。 - Martyn Lovell
@Martyn:这并不是我在使用各种编译器时的经验。例如,我使用gcc,只需尝试“-O0 -g -Wall”参数,还是会得到警告(当没有使用“main”进行测试时)。 - T.J. Crowder
2
这是因为标准特别允许 main 函数没有返回语句。如果省略了它,结果就好像最后有一个 return 0; 一样。 - Xeo

2

您可以使用以下编译器选项将警告转换为错误:

-Wreturn-type -Werror=return-type

请查看此链接


1
据我所记,Visual Studio 2008会警告你存在“没有返回值的执行路径”。这在某种意义上是允许的,就像“C++不会阻止你自己给自己惹麻烦一样”。因此,你需要自己思考,而不是依赖编译器。

1
明显的答案是:因为这不是错误。只有在x为假且调用者使用返回值时才是错误,这两个情况都不能在编译器中确定,至少在一般情况下是如此。
在这种特殊情况下(返回指针),要求所有路径都使用return并不太困难;Java就是这样做的。然而,在C++中通常不合理要求这样做,因为在C++中,您可以返回无法构造值的用户定义类型(没有默认构造函数等)。因此,程序员可能无法在他或她知道肯定不会执行的分支中提供return,而编译器也无法确定该分支是否不会执行。
大多数编译器在这种情况下会发出警告,当它能够确定流程时。但我见过的所有编译器都会在某些明显不可能掉到结尾的情况下发出警告。(例如g++和VC++都会发出警告)
int
bar( char ch )
{
    switch ( ch & 0xC0 ) {
    case 0x00:
    case 0x40:
        return 0;

    case 0x80:
        return -1;

    case 0xC0:
        return 1;
    }
}

至少使用通常的选项。尽管很明显这个函数永远不会跌落到底部。


0

关于这种编程方式,标准规定它会产生未定义行为

未定义行为是C/C++的乐趣和遗憾,但也是语言设计的基本特征之一,它允许进行许多低级别的优化,使C成为一种“高级汇编语言”(实际上并不是,只是给你一个想法)。

因此,虽然重定向到John的答案,了解GCC使用哪个开关的原因,我想指出一个非常有趣的未定义行为分析及其所有奥秘的文章:每个C程序员都应该知道的未定义行为。它是一篇非常有教育意义的阅读材料。


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