为什么在没有返回值的情况下流出非void函数的末尾不会产生编译器错误?

时间:2009-10-22 21:21:24

标签: c++ c gcc g++

自从多年前我意识到,默认情况下这不会产生错误(至少在GCC中),我一直想知道为什么?

我知道你可以发出编译器标志来产生警告,但是它不应该总是出错吗?为什么非void函数没有返回值才有意义?

评论中要求的示例:

#include <stdio.h>
int stringSize()
{
}

int main()
{
    char cstring[5];
    printf( "the last char is: %c\n", cstring[stringSize()-1] ); 
    return 0;
}

...编译。

9 个答案:

答案 0 :(得分:143)

C99和C ++标准不要求函数返回值。值返回函数中缺少的return语句将仅在0函数中定义(仅返回main)。

基本原理包括检查每个代码路径是否返回一个值是非常困难的,并且可以使用嵌入式汇编程序或其他棘手的方法设置返回值。

来自C++11草稿:

§6.6.3/ 2

  

在函数结束时流出[...]会导致值返回函数中出现未定义的行为。

§3.6.1/ 5

  

如果控件到达main的末尾而没有遇到return语句,则效果是执行

return 0;

请注意,C ++ 6.6.3 / 2中描述的行为在C中并不相同。


如果你用-Wreturn-type选项调用它,gcc会给你一个警告。

  

-Wreturn-type 每当使用返回类型定义函数时发出警告   默认为int。也警告任何   return语句没有返回值   在函数中,其返回类型不是   无效(从结尾掉下来)   函数体被认为是返回   没有价值),关于回报   带有表达式的语句   返回类型为void的函数。

     

此警告由 -Wall 启用。


就像好奇心一样,看看这段代码的作用:

#include <iostream>

int foo() {
   int a = 5;
   int b = a + 1;
}

int main() { std::cout << foo() << std::endl; } // may print 6

此代码具有正式的未定义行为,并且在实践中它依赖于calling conventionarchitecture。在一个特定的系统上,使用一个特定的编译器,返回值是最后一次表达式求值的结果,存储在该系统处理器的eax寄存器中。

答案 1 :(得分:42)

默认情况下,gcc不会检查所有代码路径是否返回值,因为通常无法执行此操作。它假设你知道你在做什么。考虑使用枚举的常见示例:

Color getColor(Suit suit) {
    switch (suit) {
        case HEARTS: case DIAMONDS: return RED;
        case SPADES: case CLUBS:    return BLACK;
    }

    // Error, no return?
}

你是程序员知道的,除了bug,这个方法总是返回一种颜色。 gcc相信你知道你在做什么,所以它不会强迫你在函数的底部放回一个。

另一方面,javac尝试验证所有代码路径都返回一个值,如果它不能证明它们都有效,则会抛出错误。 Java语言规范强制要求此错误。请注意,有时它是错误的,你必须输入一个不必要的return语句。

char getChoice() {
    int ch = read();

    if (ch == -1 || ch == 'q') {
        System.exit(0);
    }
    else {
        return (char) ch;
    }

    // Cannot reach here, but still an error.
}

这是一种哲学上的差异。 C和C ++是比Java或C#更宽松和更信任的语言,因此新语言中的一些错误是C / C ++中的警告,默认情况下会忽略或关闭一些警告。

答案 2 :(得分:13)

你的意思是,为什么流出一个值返回函数的末尾(即没有明确的return退出)不是错误?

首先,在C中,当执行代码实际使用返回的值时,函数是否返回有意义的内容才是关键。也许当你知道大部分时间你都不会使用它时,语言不想强迫你返回任何东西。

其次,显然语言规范不希望强制编译器作者检测并验证存在显式return的所有可能的控制路径(尽管在许多情况下这并不难)。此外,某些控制路径可能会导致非返回函数 - 编译器通常不知道的特征。这样的路径可能成为恼人的误报的来源。

另请注意,C和C ++在这种情况下的行为定义不同。在C ++中,只是在值返回函数的末尾流出始终是未定义的行为(无论调用代码是否使用了函数的结果)。在C中,仅当调用代码尝试使用返回的值时才会导致未定义的行为。

答案 3 :(得分:5)

在C / C ++下合法的是不从声称返回某些内容的函数返回。有许多用例,例如调用exit(-1),或调用它或抛出异常的函数。

编译器不会拒绝合法的C ++,即使你要求它不会导致UB。特别是,您要求生成 no warnings 。 (默认情况下,Gcc仍会打开一些,但添加时似乎与新功能对齐而不是旧功能的新警告)

更改默认的no-arg gcc以发出一些警告可能是现有脚本或制作系统的重大变化。精心设计的-Wall和处理警告,或切换个别警告。

学习使用C ++工具链是学习成为C ++程序员的障碍,但C ++工具链通常是由专家编写的。

答案 4 :(得分:1)

在某些有限的罕见情况下,从非void函数的末尾流出而不返回值可能很有用。类似于以下特定于MSVC的代码:

double pi()
{
    __asm fldpi
}

此函数使用x86汇编返回pi。与GCC中的汇编不同,我不知道如何使用return来做到这一点而又不增加结果开销。

据我所知,主流C ++编译器至少应针对明显无效的代码发出警告。如果我将pi()的正文设为空,则GCC / Clang将报告警告,而MSVC将报告错误。

人们在某些答案中提到了异常和exit。这些不是正当理由。抛出异常或调用exit,都会 结束该函数的执行。而且编译器知道这一点:在exit的空主体中编写throw语句或调用pi()将停止编译器的任何警告或错误。

答案 5 :(得分:0)

在什么情况下不会产生错误?如果它声明了一个返回类型并且没有返回某些内容,那对我来说听起来像是一个错误。

我能想到的一个例外是main()函数,它根本不需要return语句(至少在C ++中;我没有任何一个C标准方便)。如果没有返回,它将表现为return 0;是最后一个语句。

答案 6 :(得分:0)

听起来你需要打开编译器警告:

$ gcc -Wall -Wextra -Werror -x c -
int main(void) { return; }
cc1: warnings being treated as errors
<stdin>: In function ‘main’:
<stdin>:1: warning: ‘return’ with no value, in function returning non-void
<stdin>:1: warning: control reaches end of non-void function
$

答案 7 :(得分:0)

我相信这是因为遗留代码(C从不需要返回语句,C ++也是如此)。可能依赖于“功能”的巨大代码库。但至少有-Werror=return-type 许多编译器上的标志(包括gcc和clang)。

答案 8 :(得分:-2)

这是c99中的约束违规,但不是c89中的约束。对比:

C89:

  

3.6.6.4 return陈述

     

约束

     

带有表达式的return语句不应出现在   返回类型为void的函数。

C99:

  

6.8.6.4 return陈述

     

约束

     

带有表达式的return语句不应出现在返回类型为void的函数中。没有表达式的return语句只能出现在返回类型为void的函数中。

即使在--std=c99模式下,gcc也只会发出警告(尽管无需启用其他-W标志,如默认情况下或c89 / 90中所示。

编辑以在c89中添加“,到达终止函数的}等效于 在没有表达式“(3.6.6.4)的情况下执行return语句。但是,在c99中,行为未定义(6.9.1)。