为什么这个 C + + 代码片段要编译(非 void 函数不返回值)

我今天早上在我的一个图书馆里找到了这个:

static tvec4 Min(const tvec4& a, const tvec4& b, tvec4& out)
{
tvec3::Min(a,b,out);
out.w = min(a.w,b.w);
}

我预计会出现编译器错误,因为这个方法不返回任何内容,而且返回类型不是 void

我只想到两件事

  • 在唯一调用此方法的地方,没有使用或存储返回值。(这个方法应该是 void-tvec4返回类型是一个复制粘贴错误)

  • 正在创建一个默认构造的 tvec4,这看起来有点不像,哦,C + + 中的其他所有东西。

我还没有找到 C + + 规范中解决这个问题的部分。

更新

一些环境中,这会在 VS2012中产生一个错误。

19600 次浏览

这是 C + + 11标准草案6.6.3 返回声明2中的 未定义行为,它说:

[ ... ]从函数的结尾流出等价于没有值的返回,这导致返回值函数的未定义行为。[...]

这意味着编译器通常没有义务提供错误或警告,因为在所有情况下都难以诊断。我们可以从 1.3.24节标准草案中 未定义行为的定义中看到这一点,该定义说:

允许的未定义行为包括完全忽视情况导致不可预测的结果,在翻译或程序执行过程中按照有记录的环境特征行事(有或没有发布诊断信息) ,终止翻译或执行(发布诊断信息)。[...]

虽然在这种情况下,我们可以让 gccclang使用 -Wall标志生成一个衰减,这给我一个类似的警告:

警告: 控制到达非空函数的末端[-Wreturn-type ]

我们可以使用 -Werror=return-type标志将此特定警告转换为错误。我也喜欢在我自己的个人项目中使用 -Wextra -Wconversion -pedantic

正如 ComicSansMS 在 视觉工作室中提到的,这段代码将生成 C4716,默认情况下这是一个错误,我看到的消息是:

Error C4716: ‘ Min’: 必须返回一个值

如果不是所有的代码路径都返回一个值,那么它将生成 C4715,这是一个警告。

使用 -Wreturn-type选项编译代码:

$ g++ -Wreturn-type source.cpp

这会给你 警告。如果你也使用 -Werror,你可以把这个警告变成 错误:

$ g++ -Wreturn-type -Werror source.cpp

注意,这将把 所有警告变成错误。因此,如果您想要特定警告的错误,比如说 -Wreturn-type,只需键入 return-type而不键入 -W部分:

$ g++ -Werror=return-type source.cpp

一般来说,你应该总是使用 -Wall选项,其中包括最常见的警告 & mash; 这包括缺少返回语句也。除了 -Wall之外,还可以使用 -Wextra,它包括 -Wall不包括的其他警告。

正如前面提到的,这是一个未定义行为,会给你一个编译器警告。我工作过的大多数地方都要求您打开编译器设置,将警告视为错误——这强制您的所有代码必须编译为0错误和0警告。这是一个很好的例子,说明为什么这是一个好主意。

也许可以对问题的 为什么部分进行一些阐述:

事实证明,对于 C + + 编译器来说,确定一个函数是否在没有返回值的情况下退出实际上是相当困难的。除了以显式返回语句结束的代码路径和从函数结束的代码路径之外,还必须考虑函数本身及其所有调用中的潜在异常抛出或 longjmp

虽然编译器很容易识别一个看起来可能缺少返回值的函数,但是如果 证明缺少返回值,那么它就很难识别。为了减轻编译器供应商的这种负担,标准并不要求这种情况会产生错误。

因此,编译器供应商可以自由地生成一个警告,如果他们非常确定一个函数缺少一个返回值,然后用户可以自由地忽略/掩盖这个警告,在这些罕见的情况下,编译器实际上是错误的。

? : 在一般情况下,这相当于停止问题 ,所以实际上机器不可能可靠地决定这个问题。

也许对问题的 为什么部分做一些额外的阐述。

C + + 的设计使得大量已经存在的 C 代码能够以最少的修改进行编译。不幸的是,C 本身支付了类似的义务,最早的标准前的 C,甚至没有 void关键字,而是依赖于一个默认的返回类型的 int。C 函数通常会返回值,当编写的代码表面上类似于 Algol/Pascal/Basic 过程,但没有任何 return语句时,该函数就会返回堆栈中留下的任何垃圾。调用方和被调用方都没有以可靠的方式分配垃圾的值。如果每个调用者都忽略了垃圾,那么一切都很好,C + + 继承了编译这些代码的道德义务。

(如果调用方使用返回值,则代码的行为可能不确定,类似于处理未初始化的变量。编译器是否可以在假设的 C 语言后继语言中可靠地识别出这种差异?这几乎不可能。调用方和被调用方可能位于不同的编译单元中。)

隐式 int只是这里涉及的 C 遗产的一部分。根据参数的不同,“调度程序”函数可能从某些代码分支返回各种类型,而从其他代码分支返回没有任何有用的值。这样的函数通常被声明为返回一个足够长的类型来容纳任何可能的类型,调用者可能需要强制转换它或从 union中提取它。

因此,最深层次的原因可能是 C 语言创建者认为不返回任何值的过程只是函数返回值的一个不重要的特例; 最古老的 C 语言中的 对函数调用的类型安全缺乏关注加剧了这个问题。

虽然 C + + 确实破坏了与 C (例子)的一些最糟糕的方面的兼容性,但是愿意编译一个没有值的返回语句(或者函数末尾隐含的无值返回)并不是其中之一。

这更像是标准的 C + + 规则/特性,它往往对事物更加灵活,而且更接近于 C。

但是当我们谈到编译器、 GCC 或 VS 时,它们更多的是用于专业用途和各种开发目的,因此根据您的需要制定了更严格的开发规则。

这也是有意义的,我个人的观点,因为语言是所有关于特性和它的使用,而编译器定义的规则,最佳和最佳的方式使用它根据您的需要。

正如上面提到的,编译器有时会给出错误,有时会给出警告,而且它还可以跳过这些警告等等,这表明编译器可以以最适合我们的方式自由地使用语言及其特性。

除此之外,还有其他几个问题提到了这种在没有 return语句的情况下返回结果的行为。一个简单的例子是:

int foo(int a, int b){ int c = a+b;}


int main(){
int c = 5;
int d = 5;


printf("f(%d,%d) is %d\n", c, d, foo(c,d));


return 0;
}

这种异常是否可能是由于堆栈属性,更具体地说:

零地址机

在零地址机器中,假定两个操作数都位于默认位置的位置。 这些机器使用堆栈作为输入操作数的源,结果返回到 Stack 是所有处理器都支持的 LIFO (后进先出)数据结构,无论 或者不是,它们是零地址机器。顾名思义,堆栈上的最后一项 是要从堆栈中取出的第一项。< strong > 此类机器上的所有操作都假定所需的输入操作数是顶部的 堆栈上的两个值。操作的结果放在堆栈的顶部。

除此之外,为了访问内存读写数据,相同的寄存器被用作数据源和目标寄存器(DS (数据段)寄存器) ,它首先存储计算所需的变量,然后存储返回的结果。

注:

通过这个答案,我想讨论一个可能的解释,在机器(指令)级别的奇怪行为,因为它已经有一个上下文,并涵盖了足够广泛的范围。