昨天,我发现自己编写了这样的代码:
SomeStruct getSomeStruct()
{
SomeStruct input;
cin >> input.x;
cin >> input.y;
}
当然忘记实际返回我刚创建的结构。奇怪的是,这个函数返回的 结构中的值被初始化为零(当使用g ++编译时)。这只是一个巧合还是另一个SomeStruct被隐式创建和初始化?
答案 0 :(得分:20)
声明返回值的函数(不显式返回值)的结尾会导致未定义的后果。对于gcc,您应该从-Wall
命令行开关开始,该开关打开最有用的警告。控制所需警告的特定gcc警告是-Wreturn-type
(包含在-Wall
中,我只是提到这一点是为了完整性)。
启用警告后,您还应使用-Werror
将警告视为错误,并在检测到错误时停止构建。
答案 1 :(得分:8)
大多数现代CPU架构的调用约定指定一个特定的寄存器,以将函数返回值传递回调用者。调用者进行函数调用,然后使用指定的寄存器作为返回值。如果您没有显式返回值,则调用者仍将使用该寄存器中的任何垃圾。
编译器还将使用它可用于函数内部计算的所有寄存器。指定用于保存返回值的寄存器也将用于函数内的misc计算。因此,当您忘记指定返回值时,发现奇迹般地将错误返回给调用者的情况并不罕见:编译器使用该寄存器来存储您的对象。
不幸的是,即使对函数进行微不足道的更改也会导致寄存器分配发生变化,因此返回值将变为真正的垃圾。
答案 2 :(得分:7)
是否隐式创建并初始化了另一个SomeStruct?
考虑如何返回结构。如果x
和y
都是32位,则它太大而不适合32位架构上的寄存器,这同样适用于64位架构上的64位值(@ Denton Gentry的回答提到了返回的值是多么简单),因此必须在某处分配。为此使用堆将是浪费,因此必须在堆栈上分配它。但它不能位于getSomeStruct
函数的堆栈帧上,因为在函数返回后它不再有效。
编译器通过向调用函数传递一个隐藏指针指向分配给它的空间,调用者告诉被调用函数将结果放在哪里(可能是调用者堆栈的某个位置)。因此,将其设置为零的位置在调用者上,而不是在getSomeStruct
函数上。
还有一些优化,例如“命名值返回优化”,可以省略额外的副本。所以,如果你使用了缺失的return
,结果将直接在调用者分配的空间上创建,而不是创建临时的并复制它。
要了解更多有关正在发生的事情,您必须查看调用者函数。它是否正在初始化(为零)一个“空”SomeStruct
,您稍后会为其分配getSomeStruct
函数的返回值?或者它正在做其他事情?
答案 3 :(得分:3)
我发现这很有趣。使用默认选项时,以下编译器在编译GetSomeStruct()
函数时具有以下行为:
Microsoft VC,所有版本(无论如何都是VC6):
<强> error C4716: 'getSomeStruct' : must return a value
强>
数字火星:
<强> Warning 18: implied return of getSomeStruct at closing '}' does not return value
强>
Comeau:
<强> warning: missing return statement at end of non-void function "getSomeStruct"
强>
gcc:
没有错误或警告
鉴于标准中的以下几句话(6.6.3第2段):
没有的退货声明 表达式只能用于 不返回值的函数, 也就是说,返回一个函数 type void,构造函数(12.1)或a 析构函数(12.4)。 ......流了下来 函数的结尾相当于 没有价值的回报;这个结果 在未定义的行为中 价值回归功能。
我想说在这种情况下,编译器没有理由不给出错误。为什么这么多编译器只给出警告或根本没有诊断?
答案 4 :(得分:2)
对我来说,编译器不允许它:http://codepad.org/KkzVCesh
答案 5 :(得分:1)
您没有收到任何警告,因为您没有启用-Wall -Werror
。 (如其他答案所述)
但是我认为你可能得到了一个零填充结构,因为堆栈对象是在调用函数中默认构造的,可能带有显式零参数,或者由于堆栈上的零而导致?