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

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

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

我期望编译器错误,因为这个方法不返回任何东西,并且返回types不是void

想到的唯一的两件事是

  • 在唯一调用此方法的地方,返回值未被使用或存储。 (这种方法应该是voidtvec4返回types是复制粘贴错误)

  • 一个默认构build的tvec4正在创build,这似乎有点不像,哦,C ++中的其他一切。

我还没有find解决这个问题的C ++规范的一部分。 参考(公顷)表示赞赏。

更新

某些情况下,这会在VS2012中产生一个错误。 我没有缩小细节,但是很有趣。

这是C ++ 11草案标准部分中未定义的行为 6.6.3 返回语句段落2说:

stream出函数的结尾相当于没有值的返回; 这会导致值返回函数中的未定义行为。 […]

这意味着编译器没有义务提供一个错误,也不是一个警告,因为在任何情况下都很难诊断。 我们可以从1.3.24节的标准草案中的未定义行为的定义中看到:

[…]允许未定义的行为范围从完全忽略情况与不可预知的结果,在翻译或程序执行过程中performance为环境特征(无论是否发布诊断信息),终止翻译或执行(发出诊断信息)。

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

警告:控制到达非void函数结束[-Wreturn-type]

我们可以使用-Werror=return-type标志将这个特定的警告变成一个错误。 我也喜欢使用-Wextra -Wconversion -pedantic为我自己的个人项目。

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

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

并且在不是所有代码path都会返回一个值的情况下,它会生成C4715 ,这是一个警告。

也许有一些关于为什么问题的一部分的详细说明:

事实certificate,C ++编译器实际上很难确定一个函数是否没有返回值。 除了以显式返回语句结束的代码path以及从函数结束的代码path之外,还必须考虑函数本身及其所有被调用者中可能的exceptionthrows或longjmp s。

虽然编译器很容易识别看起来可能缺less返回值的函数,但certificate缺less返回值是相当困难的。 为了解除这个负担的编译器厂商,标准并不要求这个产生错误。

所以编译器厂商可以自由地产生一个警告,如果他们确信一个函数缺less一个返回值,那么在编译器实际上是错误的罕见情况下,用户可以自由地忽略/屏蔽该警告。

†: 在一般情况下,这相当于暂停问题 ,所以机器实际上不可能确定这一点。

-Wreturn-type选项编译你的代码:

 $ g++ -Wreturn-type source.cpp 

这会给你警告 。 如果您使用-Werror则可以将该警告转为错误

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

请注意,这会将所有警告转换为错误。 所以,如果你想为特定的警告错误,比如说-Wreturn-type ,只需return-type没有-W部分的return-type为:

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

一般来说,你应该总是使用-Wall选项,其中包括最常见的警告 – 这也包括缺less的return语句。 与-Wall ,您也可以使用-Wextra ,其中包括其他警告,不包含在-Wall

也许一些额外的详细说明为什么部分的问题。

C ++的devise使得大量的预先存在的C代码体以最小的变化进行编译。 不幸的是,C本身也在为最早的标准C语言付出类似的责任,甚至没有void关键字,而是依赖于int的默认返回types。 C函数通常会返回值,并且只要代码表面上类似于Algol / Pascal / Basic过程的代码在没有任何return语句的情况下编写,那么该函数就会返回堆栈中留下的任何垃圾。 呼叫者和被呼叫者都不能以可靠的方式分配垃圾的价值。 如果垃圾被每个调用者忽略,一切都很好,C ++inheritance了编译这些代码的道德义务。

(如果调用者使用返回值,代码可能会有不确定的行为,类似于处理一个未初始化的variables,这种差异是否可以被编译器可靠地识别出来,以C为假设的后继语言呢?这是不可能的。调用者和被调用者可能处于不同的编译单元中。)

隐式int仅仅是这里涉及的C遗产的一部分。 根据参数的不同,“调度程序”函数可能会从某些代码分支中返回多种types,并且不会从其他代码分支中返回有用的值。 这样的函数通常会被声明为返回一个足够长的types来保存任何可能的types,调用者可能需要将其转换或从union提取出来。

所以最深的原因可能是C语言创build者认为不返回任何值的程序只是一个不重要的函数特例; 这个问题由于对最古老的C语言中函数调用的types安全缺乏重视而变得更加严重。

虽然C ++没有打破与C( 例子 )最糟糕的一些方面的兼容性,但是没有一个值(或者在函数结尾的隐式无价值返回值)编译return语句的意愿并不是其中的一个。

如前所述,这是未定义的行为,并会给你一个编译器警告。 我工作的大部分地方都要求您打开编译器设置来将警告视为错误 – 这会强制您的所有代码必须编译为0错误和0个警告。 这是一个很好的例子,为什么这是一个好主意。

这是更多的标准C ++规则/function,它往往是灵活的东西,往往是更接近C.

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

我的个人意见也是有道理的,因为语言是关于function和用法的,而编译器则是根据需要定义最佳和最好的使用规则。

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

除此之外,还有几个其他问题提到了返回结果而没有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; } 

难道这个exception是由于堆栈属性,更具体地说:

零地址机器

在零地址机器中, 两个操作数的位置都假定为默认位置 。 这些机器使用堆栈作为input操作数的来源,结果返回到堆栈。 堆栈是所有处理器都支持的LIFO(后进先出)数据结构,无论它们是否为零地址机器。 顾名思义,放在堆栈上的最后一个项目是第一个要从堆栈​​中取出的项目。 这种types的机器上的所有操作都假定所需的input操作数是堆栈中的前两个值。 操作的结果放在堆栈顶部。

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

注意:

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