首页 文章

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

提问于
浏览
149

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

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

评论中要求的示例:

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

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

...编译 .

8 回答

  • 142

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

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

    来自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的函数(从函数体的末尾掉下来被认为是没有值返回),以及一个函数中带有表达式的return语句return-type无效 . -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 寄存器中 .

  • 0

    默认情况下,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

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

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

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

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

  • 4

    根据C / C而言,如果不从声称要归还某些东西的函数返回,那么它是合法的 . 有许多用例,例如调用 exit(-1) ,或者调用它或抛出异常的函数 .

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

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

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

  • 13

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

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

  • 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
    $
    
  • 0

    我相信这是因为遗留代码(C从不需要返回语句,所以做了C) . 可能有很大的代码库依赖于"feature" . 但至少在许多编译器上都有 -Werror=return-type 标志(包括gcc和clang) .

  • 42

    它是c99中的约束违规,但不是c89中的约束 . 对比:

    C89:

    3.6.6.4返回语句约束带有表达式的return语句不应出现在返回类型为void的函数中 .

    C99:

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

    即使在 --std=c99 模式下,gcc也只会发出警告(尽管不需要启用额外的 -W 标志,如默认情况下或c89 / 90中所要求的那样) .

    编辑以在c89中添加“,到达终止函数的 } 等同于执行不带表达式的 return 语句”(3.6.6.4) . 但是,在c99中,行为未定义(6.9.1) .

相关问题