为什么不返回值而从非 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;
}

编译。

78750 次浏览

在什么情况下它不会产生错误?如果它声明了一个返回类型,但是没有返回任何东西,那么在我看来就是一个错误。

我能想到的一个例外是 main()函数,它根本不需要 return语句(至少在 C + + 中; 我手边没有任何一种 C 标准)。如果没有返回,那么它的行为就好像 return 0;是最后一个语句。

您的意思是,为什么从返回值函数的末尾流出(即在没有显式 return的情况下退出)不是一个错误?

首先,在 C 语言中,函数是否返回有意义的值只有在执行代码实际上返回值为 用途时才是关键的。也许语言并不想强迫您返回任何内容,因为您知道无论如何大部分时间都不会使用它。

其次,显然语言规范不想强迫编译器作者检测和验证所有可能的控制路径,以确定显式 return的存在(尽管在许多情况下这并不难做到)。此外,一些控制路径可能导致到 不返回函数-特性,这是通常不知道的编译器。这样的路径可能成为恼人的假阳性的来源。

另请注意,在本例中,C 和 C + + 对行为的定义不同。在 C + + 中,仅仅从返回值函数的末尾流出,总是未定义行为(不管调用代码是否使用函数的结果)。在 C 语言中,只有当调用代码尝试使用返回值时才会出现未定义行为。

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

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

默认情况下,gcc 不会检查所有代码路径是否返回一个值,因为通常这是不可能做到的。它假设你知道自己在做什么。考虑一个使用枚举的常见示例:

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


// Error, no return?
}

程序员应该知道,除非有 bug,否则这个方法总是返回一种颜色。Gcc 相信您知道自己在做什么,所以它不会强迫您在函数的底部放置一个 return。

另一方面,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.
}

这是哲学上的区别。与 Java 或 C # 相比,C 和 C + + 是更宽容和更可信的语言,因此新语言中的一些错误是 C/C + + 中的警告,一些警告在默认情况下被忽略或关闭。

这在 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中,“到达终止函数的 }相当于 在没有表达式的情况下执行 return语句”(3.6.6.4)。

C99和 C + + 标准要求非 void函数返回一个值,除了 main。将定义 main中缺少的 return 语句(以返回 0)。在 C + + 中,如果执行实际上到达了 main以外的非 void函数的末尾,那么它就是未定义的行为; 而在 C 中,如果调用者 用途返回值,那么它只是 UB。

这意味着函数可能看起来像是到达了结尾而没有返回一个值,但实际上不能到达结束的 }John Kugelman 的回答展示了一些示例,比如从 if的一侧调用的 noreturn 函数。只有当执行实际到达终点时没有提前到达 return,才是未定义的行为。其基本原理包括: 检查每个实际代码路径是否返回一个值是相当困难的(在不知道哪些函数永远不会返回的情况下) ,因此像您的示例那样对 编译函数进行调用并不违法,只是像您的 main那样实际调用它。

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

来自 C + + 11草案:

6.6.3/2

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

3.6.1/5

如果控制到达 main的末尾时没有遇到 return语句,那么结果就是执行

return 0;

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


如果调用 gcc with-Wreturn-type 选项,gcc 将给出警告。

- Wreturn-type Warn 每当一个函数被定义为返回类型为 默认设置为 int 没有返回值的 return 语句 返回类型不是 (从... 的末端掉下来) 函数体被认为是返回的 (没有值) ,以及关于返回值 语句中带有一个表达式 返回类型为 void 的函数。

This warning is enabled by .


只是出于好奇,看看这段代码做了什么:

#include <iostream>


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指令,因此执行将落入。文本部分。当然,未定义的行为意味着任何事情都有可能发生。

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

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

编译器不会拒绝合法的 C + + ,即使它导致 UB,如果你不要求它。特别是,您要求生成 没有警告。(Gcc 在默认情况下仍然开启一些功能,但是当添加这些功能时,它们似乎与新功能保持一致,而不是与旧功能的新警告保持一致)

更改默认的 no-arg gcc 以发出一些警告可能是对现有脚本或 make 系统的重大更改。设计良好的 -Wall可以处理警告,也可以切换单个警告。

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

在某些有限和罕见的情况下,不返回值而从非 void 函数的末尾流出可能是有用的。比如下面的 MSVC 特定代码:

double pi()
{
__asm fldpi
}

此函数使用 x86程序集返回 π。与 GCC 中的汇编不同,我不知道如何使用 return来完成这项工作,而不会在结果中涉及开销。

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

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

我收到警告是因为我忘记加上声明了 Itr = itr-> currentNode; 基本上错过了这个语句,函数进入了无限循环,没有返回值,这就是我在编译时收到警告的原因

void* list_get(list* lst, int idx){


node* itr = lst->head;
    

if (idx >= lst->size){
printf("list out of index");
exit(1);
}
    

while(itr != NULL){
if(itr->index == idx){
return  itr->element;
}
itr = itr->currentNode;
     

}




}

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函数之外,从函数的末尾流出会导致未定义行为,而不管调用者对结果做了什么。