为什么这个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
。
想到的唯一的两件事是
-
在唯一调用此方法的地方,返回值未被使用或存储。 (这种方法应该是
void
–tvec4
返回types是复制粘贴错误) -
一个默认构build的
tvec4
正在创build,这似乎有点不像,哦,C ++中的其他一切。
我还没有find解决这个问题的C ++规范的一部分。 参考(公顷)表示赞赏。
更新
在某些情况下,这会在VS2012中产生一个错误。 我没有缩小细节,但是很有趣。
这是C ++ 11草案标准部分中未定义的行为 6.6.3
返回语句段落2说:
stream出函数的结尾相当于没有值的返回; 这会导致值返回函数中的未定义行为。 […]
这意味着编译器没有义务提供一个错误,也不是一个警告,因为在任何情况下都很难诊断。 我们可以从1.3.24
节的标准草案中的未定义行为的定义中看到:
[…]允许未定义的行为范围从完全忽略情况与不可预知的结果,在翻译或程序执行过程中performance为环境特征(无论是否发布诊断信息),终止翻译或执行(发出诊断信息)。
虽然在这种情况下,我们可以同时使用gcc
和clang
来使用-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,然后返回返回的结果。
注意:
有了这个答案,我想讨论一下机器(指令)级别的奇怪行为的一个可能的解释,因为它已经有了一个上下文,而且它的覆盖范围足够广泛。