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

新手上路,请多包涵

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

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

评论中要求的示例:

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

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

…编译。

原文由 Catskul 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 1.1k
2 个回答

C99 和 C++ 标准要求非 void 函数返回值,但 main 除外。将定义 main 中缺少的 return 语句(返回 0 )。在 C++ 中,如果执行实际上到达了非 void 函数的结尾,而不是 main ,则这是未定义的行为,而在 C 中,如果调用者 使用 返回值,则只有 UB。

这意味着函数看起来可能会在没有返回值的情况下到达结尾,但实际上无法到达结束 }John Kugelman 的回答 显示了一些示例,例如从 if 的一侧调用的 noreturn 函数。如果执行实际上没有达到 return 更早,这只是未定义的行为。基本原理包括检查每个真实的代码路径是否返回一个值是非常困难的(不知道哪些函数永远不会返回),所以像你的例子那样 编译 一个函数并不违法,只是像你的 main 那样实际调用它—— --- 确实。

作为扩展,至少有一个编译器 (MSVC) 允许使用 inline assembly 设置返回值,但大多数其他编译器仍然需要在使用 inline asm 的函数中使用 return 语句。

来自 C++11 草案:

§ 6.6.32

从函数的末尾流出 […] 会导致返回值的函数出现未定义的行为。

§ 3.6.15

如果控制到达 main 而没有遇到 return 语句,效果是执行

> return 0;
>
> ```

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

* * *

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

> **-Wreturn-type** 每当使用默认为 int 的返回类型定义函数时发出警告。还要警告在返回类型不是 void 的函数中任何没有返回值的返回语句(从函数体的末尾落下被认为返回没有值),以及在函数中带有表达式的返回语句返回类型是无效的。
>
> 此警告由 **-Wall** 启用。

* * *

出于好奇,看看这段代码的作用:

#include

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

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

”`

此代码具有形式上未定义的行为,实际上它依赖于 调用约定体系结构。在具有特定编译器的特定系统上,如果禁用优化,则返回值是最后一次表达式评估的结果,存储在该系统处理器的 eax 寄存器中。

这似乎是 GCC 内部禁用优化的结果,因为在这种情况下,如果它需要任何返回值寄存器来实现语句, 它就会选择返回值寄存器。在 C++ 模式下启用优化后,GCC 和 clang 假定此执行路径不可访问,因为它包含未定义的行为。他们甚至不发出 ret 指令,因此执行落入 .text 部分中的下一个函数。当然,未定义的行为意味着任何事情都可能发生。

原文由 Fernando N. 发布,翻译遵循 CC BY-SA 4.0 许可协议

C 和 C++ 有不同的规则。


C 语言的规则是,如果一个函数的关闭 } 返回一个非 void并且 调用者尝试使用该值,则行为未定义。只要调用者不使用该值,只要从函数末尾掉下来就具有明确定义的行为。

可以要求所有可能的控制路径在离开函数之前执行 return 语句,但 C 传统上不要求编译器进行这种代码分析。 (无论如何,许多编译器都会进行这种分析,并在适当的时候发出警告。)

允许从非 void 函数结束的主要原因是历史原因。 K&R C(Kernighan 和 Ritchie 的 1978 年第一版书中描述的版本,在 1989 年 ANSI 和 1990 ISO C 标准之前)没有 void 关键字或类型。在 1999 ISO C 标准之前,C 有“隐式 int ”规则,这意味着您可以声明或定义一个没有显式返回类型的函数,它会返回一个 int 结果。

在 K&R C 中,如果您想要一个不返回结果的函数,您可以在没有显式返回类型的情况下定义它,并且根本不返回值:

 #include <stdio.h>

do_something() {
    printf("Not returning a value\n");
}

int main() {
    do_something();
    return 0;
}

该函数实际上会返回一些垃圾 int 调用者会悄悄忽略的值。

在现代 C 中,你会写:

 #include <stdio.h>

void do_something(void) {
    printf("Not returning a value\n");
}

int main(void) {
    do_something();
}

这保证了调用者 不能 尝试使用返回的值。从 C89/C90 开始,该语言仍然支持旧样式以避免破坏现有代码。当隐式 int 规则在 C99 中被删除时,对非 void 函数未能返回值的要求没有改变(并且大多数 C99 和更高版本的编译器仍然支持隐式 int 默认规则,可能带有警告,所以旧的 K&R C 代码仍然可以编译)。


在 C++ 中,从构造函数、析构函数、 void 函数或 main 以外的函数的末尾流出会导致未定义的行为,无论调用者试图做什么结果。

原文由 Keith Thompson 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题