f() 不会返回,即使它的签名说它应该。为什么允许它编译的原因是什么?C标准是否有理由不要求编译器使其失败?
我知道这是未定义的行为,但为什么首先允许它?有历史原因吗?
double f(){}
int main()
{
f();
return 0;
}
C标准是否有理由不要求编译器使其失败?
通过调用未定义的行为,C 标准允许编译器不那么复杂。确实有一些情况,比如if
语句,很难说函数有没有返回值:
int f(int n)
{
if (n > 0) return 1;
}
f(5)
,编译器很容易说函数是正确的。 f(-5)
,也很容易检测到未定义的返回值。但是,例如,如果参数来自用户输入,编译器应该如何知道函数是否返回值?由于这可能是一个有效或错误的程序,C 标准允许编译器做他们想做的事。C 被设计为尽可能智能和简单。
如果编译器不能证明函数总是返回一个有意义的值,编译器当然可以分析函数的所有代码路径并拒绝程序。我想该标准没有强制要求的原因是,在过去,编译器远没有我们今天使用的那么复杂。
当然,使用这种函数的返回值是未定义的行为:
如果到达终止函数的 },并且调用者使用了函数调用的值,则行为未定义。
很容易看出你的函数将在没有返回值的情况下结束。它不会返回,也不会调用任何可能阻止它到达末尾的代码(如abort()
)。
事实上,您的程序在 C99 中没有未定义的行为,因为调用者没有使用丢失的返回值。6.9.1/12:
如果到达终止函数的},并且调用者使用了函数调用的值,则行为未定义。
所以你的代码风格有问题,但定义明确。
C++ 标准更改了规则并在[diff.stat]
. 它说 C 版本的规则是为了支持在 C 不区分int
return 和void
return 的日子里写回的代码。因此,您的代码首先定义行为的原因是“遗留”。也就是说,AFAIK C 始终区分double
返回和int
返回,因此如果在正确的时间完成,它可能会使返回函数的 UBdouble
落到最后。
抛开是否使用返回值,考虑一个诡计函数:
double f() {
if (g()) exit();
}
这个函数也不包含 return 语句,但如果实际上g
总是返回一个真值或根本不返回,则不会到达函数的末尾。因此,即使使用它的返回值,这个函数也应该被接受,根据一般的 C 标准原则,你应该知道你在做什么并且你说的是什么意思。如果g
在不同的 TU 中定义,那么您可能比编译器更了解它。
即使不是出于遗留原因,我也很确定标准根本不会被打扰添加文本以定义编译器需要检测哪些非返回场景。这取决于实现的质量——如果可以在编译时确定您的函数不可能避免 UB,那么尽管不需要诊断,编译器可能仍会发出警告。就此而言,当根据通用 C实现者的原则定义行为时,它偶尔会发出警告,即有些事情太愚蠢以至于用户无法合理地理解它们。
因为编译器无法判断函数是否在运行时返回。