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

3Da*_*ave 128 c++ methods c++11 visual-studio-2012

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

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

我期望编译器错误,因为此方法不返回任何内容,并且返回类型不返回void.

想到的唯一两件事是

  • 在调用此方法的唯一位置,不使用或存储返回值.(此方法应该是void- tvec4返回类型是复制和粘贴错误)

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

我还没有找到解决这个问题的C++规范部分.参考文献(ha)表示赞赏.

更新

某些情况下,这会在VS2012中生成错误.我没有缩小具体细节,但它仍然很有趣.

Sha*_*our 153

这是来自C++ 11草案标准部分未定义行为.返回声明2段说:6.6.3

[...]离开函数末尾相当于没有值的返回; 这会导致值返回函数中的未定义行为.[...]

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

[...]允许的未定义行为包括完全忽略不完全结果的情况,在翻译或程序执行期间以环境特征(有或没有发出诊断消息)的文件化方式行事,终止翻译或执行(发布诊断信息).[...]

虽然在这种情况下我们可以获得两者gccclang使用-Wall标志生成一个wanring ,这给了我类似的警告:

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

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

正如ComicSansMS在Visual Studio中提到的,这段代码会生成C4716,默认情况下是一个错误,我看到的消息是:

错误C4716:'Min':必须返回一个值

并且在并非所有代码路径都返回值的情况下,它将生成C4715,这是一个警告.

  • 为了记录,VC生成[C4716](http://msdn.microsoft.com/en-us/library/ft5xye74.aspx)或[C4715](http://msdn.microsoft.com/en-us/ library/6deaf4k9.aspx)在这种情况下.前者甚至默认被视为编译器错误. (10认同)
  • 如果`static`在那里,但函数没有在文件的其他地方引用,那么编译器可能完全优化了函数,并且没有费心去检查`return`.通过删除`static`,可以从其他文件中引用该函数.这意味着编译器无法对其进行优化,并进入寻找"返回"的部分. (3认同)
  • @dpassage证实它正在优化. (2认同)

Com*_*sMS 58

也许在一些阐述为什么这个问题的一部分:

事实证明,对于C++编译器来说,确定函数是否在没有返回值的情况下退出是非常困难的.除了以显式返回语句结尾的代码路径以及从函数末尾开始的代码路径之外,还必须考虑longjmp函数本身及其所有被调用者中的潜在异常抛​​出或s.

虽然编译器很容易识别看起来可能缺少返回的函数,但要证明它缺少返回却相当困难.为了解除编译器供应商的这种负担,标准不要求它产生错误.

所以编译器厂商可以自由地产生一个警告,如果他们是相当肯定的功能缺少回报,用户就可以自由地忽略/掩盖在极个别情况下,编译器实际上是错误的警告.

†:在一般情况下,这相当于暂停问题,因此机器实际上不可能可靠地决定这一点.

  • 谨慎地提一下,决定一个函数是否到达它的块结束或者早期返回(通过longjmp除外)等同于[停止问题](http://en.wikipedia.org/wiki/Halting_problem)在一般情况下是不可判定的 (6认同)
  • @ChristopherCreutzig简单地想象一下,调用一个驻留在不同编译单元(甚至是动态库)中的函数.由于您没有看到代码和异常不是C++中接口的一部分,因此无法确定此类函数调用是否以及如何返回. (2认同)
  • 如果我的例程留有异常,它显然不会返回任何值.但它不应该,并且发现在这种情况下它并不难.对于`longjmp`来说也是如此 - 没有任何价值,没有人试图捡起它. (2认同)
  • @ChristopherCreutzig是的,但关键是你不希望函数拒绝编译只是因为它的一个执行路径返回异常而不是返回值.由于编译器无法可靠地检测异常,因此只能因为执行路径似乎缺少返回而导致编译失败 - 可能仍然会在该路径的某处隐藏异常. (2认同)
  • @ChristopherCreutzig完全正确.我经常遇到大型switch-case语句遇到这个问题,其中每个case标签都以return形式结束.如果我然后添加一个触发错误条件的默认标签,以防万一我不小心错过了一个案例,编译器通常无法告诉默认情况也永远不会返回.所以我得到一个关于函数末尾缺少返回的警告,并且必须插入你提到的那些虚拟返回. (2认同)
  • 这就是gcc中的``noreturn``属性.以防您只使用支持它的编译器.具有该属性的函数被视为不返回.来自`<cassert>``的AFAIK``assert()``以这种方式注释,允许使用你提到的``default:`` (2认同)
  • @JonasWielicki:有一个标准属性:`[[noreturn]]`. (2认同)

Naw*_*waz 22

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

$ g++ -Wreturn-type source.cpp
Run Code Online (Sandbox Code Playgroud)

这会给你警告.如果您也使用警告,则可以将警告变为错误-Werror:

$ g++ -Wreturn-type -Werror source.cpp
Run Code Online (Sandbox Code Playgroud)

请注意,这会将所有警告变为错误.因此,如果你想要特定警告的错误,比如说-Wreturn-type,只需键入return-type不带-W部分:

$ g++ -Werror=return-type source.cpp
Run Code Online (Sandbox Code Playgroud)

通常,您应该始终使用-Wall包含最常见警告的选项 - 这也包括缺少return语句.除此之外-Wall,您还可以使用-Wextra,其中包括未包含的其他警告-Wall.

  • @TED:然后,阅读编译器的文档.大多数主流编译器等同于选项.(顺便说一句,他应该说他正在使用什么编译器,如果有兴趣知道*特定的*编译器行为). (11认同)
  • 这仅适用于certian编译器.他没有说他正在使用什么编译器. (2认同)

Jir*_*ika 21

也许在一些额外的阐述为什么这个问题的一部分.

C++的设计使得大量预先存在的C代码体以最小的更改量进行编译.不幸的是,C本身对最早的预标准C付出了类似的责任,它甚至没有void关键字,而是依赖于默认的返回类型int.C函数通常会返回值,并且只要表面上与Algol/Pascal/Basic程序类似的代码没有任何return语句,该函数就会在引擎盖下返回堆栈中剩余的垃圾.调用者和被调用者都不能以可靠的方式分配垃圾的值.如果每个调用者都忽略了垃圾,那么一切都很好,C++继承了编译这些代码的道德义务.

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

隐含int只是这里涉及的C遗产的一部分.根据参数,"调度程序"函数可能会从某些代码分支返回各种类型,并且不会从其他代码分支返回任何有用的值.通常会声明这样的函数返回一个足够长的类型来保存任何可能的类型,并且调用者可能需要将其强制转换或从中提取union.

因此,最深层次的原因可能是C语言创建者认为,不返回任何值的过程只是一个不重要的特殊功能案例; 由于缺乏对最古老的C方言中函数调用的类型安全性的关注,这个问题变得更加严重.

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

  • 有趣的是,在C99和C11中,如果调用者尝试使用返回值,则只是未定义. (4认同)
  • +1 清楚的解释。 (2认同)

Zac*_*and 12

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


归档时间:

查看次数:

13860 次

最近记录:

7 年,10 月 前