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

时间:2013-12-16 15:16:54

标签: c++ visual-studio-2012 c++11 methods

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

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 ++规范部分。参考文献(ha)表示赞赏。

更新

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

6 个答案:

答案 0 :(得分:152)

undefined behavior部分6.6.3 返回声明 2 中的C++11 draft standard表示:

  

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

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

  

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

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

  

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

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

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

  

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

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

答案 1 :(得分:22)

使用-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选项 - 这也包括缺少return语句。与-Wall一起,您也可以使用-Wextra,其中包含-Wall未包含的其他警告。

答案 2 :(得分:21)

可能会对为什么部分问题进行一些额外的阐述。

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

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

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

因此,最深层的原因可能是C语言创建者认为不返回任何值的过程只是一个不重要的特殊情况;最老的C方言lack of focus on type safety of function calls使这个问题更加严重。

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

答案 3 :(得分:12)

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

答案 4 :(得分:2)

这更像是标准的C ++规则/功能,它往往对事物很灵活,而且往往更接近C。

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

这也是有道理的,我的个人观点,因为语言完全是关于功能及其用法,而编译器根据您的需要定义了最佳和最佳使用方式的规则。

如上文所述,编译器有时会给出错误,有时会发出警告,并且可以选择跳过这些警告等,表明可以自由地使用语言及其功能,以最适合我们的方式。

答案 5 :(得分:0)

除此之外,还有其他一些问题提到了在没有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(后进先出)数据结构,所有处理器都支持这种结构   他们是零地址机器。顾名思义,最后一项放在堆栈上   是第一个从堆栈中取出的项目。 此类机器上的所有操作都假定所需的输入操作数是最高的   堆栈上有两个值。操作的结果放在堆栈顶部。

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

注意:

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