为什么 C++ 允许实际上 return 没有值的函数?

Why does C++ allow functions that don't actually return a value?

在 C++ 中,允许具有非空 return 类型且没有 return 语句的函数。所以,下面的代码将编译:

std::string give_me_a_string()
{
}

然而在C#中,这种方法是不允许的。所以,下面的代码不会编译:

public string GiveMeAString()
{
}

为什么会这样?这两种语言的设计原理是什么?

C++ 要求代码是“well-behaved”以便以定义的方式执行,但该语言不会试图比程序员更聪明——当出现可能导致未定义的情况时行为,编译器可以自由假设这种情况实际上 永远不会 在运行时发生,即使它不能通过其静态分析来证明。

Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.

调用这样的函数是合法的行为;仅在不提供值的情况下从其末端流出是未定义的。我想说允许这样做有合法的(而且主要是遗留的)原因,例如你可能正在调用一个总是抛出异常或执行 longjmp 的函数(或者有条件地这样做,但你知道它总是发生在这个位置,并且 [[noreturn]] 仅出现在 C++11 中)。

不过,这是一把 double-edged 剑,因为虽然在您知道不会发生的情况下不必提供值可能有利于进一步优化代码,但您也可以错误地省略它,类似于从未初始化的变量中读取。过去有很多这样的错误,所以这就是为什么现代编译器会警告你这一点,有时还会插入保护措施,使它在运行时稍微易于管理。

举个例子,一个过度优化的编译器可能会假设一个从不产生其 return 值的函数实际上永远不会 returns,并且它可以继续这个推理直到创建一个空的 main 方法而不是您的代码。


另一方面,C# 有不同的设计原则。它旨在被编译为中间代码,而不是本地代码,因此它的可定义性规则必须符合中间代码的规则。而CIL必须是可验证的才能在某些地方执行,所以像流出函数末尾的情况必须事先检测到。

C# 的另一个原则是在常见情况下不允许未定义的行为。由于它也比 C++ 年轻,它的优势在于假设计算机足够高效以支持比 C++ 初期情况更强大的静态分析。编译器可以负担得起检测这种情况,并且由于 CIL 必须是可验证的,因此只有两个操作是可行的:静默发出抛出异常的代码(类似于 assert false),或者完全禁止这种情况。由于C#也有吸取C++教训的优势,所以开发者选择了后者。

这仍然有它的缺点——有些辅助方法永远不会 return,而且仍然没有办法在语言中静态地表示它,所以你必须使用类似 return default; 在调用此类方法后,可能会使阅读代码的任何人感到困惑。