14

昨天,我发现自己在写这样的代码:

SomeStruct getSomeStruct()
{
    SomeStruct input;

    cin >> input.x;
    cin >> input.y;
}

当然忘记实际返回我刚刚创建的结构。奇怪的是,这个函数返回的结构中的值初始化为零(当使用 g++ 编译时)。这只是一个巧合还是另一个 SomeStruct 被隐式地创建和初始化?

4

6 回答 6

22

从声明为返回值(没有显式返回值)的函数的末尾脱落会导致未定义的后果。对于 gcc,您应该从-Wall打开最有用警告的命令行开关开始。控制您想要的警告的特定 gcc 警告是-Wreturn-type(包含在 中-Wall,为了完整起见,我只是提到这一点)。

打开警告后,您还应该使用-Werror将警告视为错误,并在检测到错误时停止构建。

于 2008-11-16T04:53:26.500 回答
9

大多数现代 CPU 架构的调用约定指定一个特定的寄存器来将函数返回值传回给调用者。调用者进行函数调用,然后使用指定的寄存器作为返回值。如果你没有显式地返回一个值,调用者仍然会使用该寄存器中发生的任何垃圾。

编译器还将使用它可用于函数内部计算的所有寄存器。指定用于保存返回值的寄存器也将用于函数内的杂项计算。因此,当您忘记指定返回值时,通常会奇迹般地将正确的值返回给调用者:编译器使用该寄存器来存储您的对象。

不幸的是,即使是对函数的微小更改也可能导致寄存器分配发生变化,因此返回值变成了真正的垃圾。

于 2008-11-16T05:34:13.143 回答
7

是否隐式地在某处创建并初始化了另一个 SomeStruct?

想想结构是如何返回的。如果xy都是 32 位,则它太大而无法放入 32 位架构上的寄存器中,同样适用于 64 位架构上的 64 位值(@Denton Gentry 的回答提到了如何返回更简单的值) ,所以它必须分配到某个地方。为此使用堆会很浪费,因此必须在堆栈上分配它。但它不能在函数的堆栈帧上getSomeStruct,因为在函数返回后它不再有效。

相反,编译器通过向被调用函数传递一个指向分配给它的空间的隐藏指针,让调用者告诉被调用函数将结果放在哪里(可能在调用者的堆栈上的某个位置)。因此,将其设置为零的位置在caller上,而不是在您的getSomeStruct函数上。

还有一些优化,比如“命名值返回优化”,可以省略额外的副本。所以,如果你使用了 missing return,结果将直接在调用者分配的空间上创建,而不是创建一个临时的并复制它。

要了解更多关于正在发生的事情,您必须查看调用者函数。它是否正在初始化(为零)一个“空” SomeStruct,您稍后将getSomeStruct函数的返回值分配给该“空”?还是它在做其他事情?

于 2008-11-16T06:15:19.957 回答
3

我觉得这很有趣。使用默认选项时,以下编译器在编译GetSomeStruct()函数时具有以下行为:

  • Microsoft VC,所有版本(从 VC6 开始):

    error C4716: 'getSomeStruct' : must return a value

  • 数字火星:

    Warning 18: implied return of getSomeStruct at closing '}' does not return value

  • 科莫:

    warning: missing return statement at end of non-void function "getSomeStruct"

  • 海湾合作委员会:

    没有错误或警告

鉴于标准中的以下几句话(6.6.3 第 2 段):

没有表达式的 return 语句只能用于不返回值的函数,即返回类型为 void 的函数、构造函数 (12.1) 或析构函数 (12.4)。...从函数的末尾流出相当于没有值的返回;这会导致值返回函数中的未定义行为。

我想说编译器在这种情况下没有理由不给出错误。为什么这么多编译器只给出警告或根本不给出诊断?

于 2008-11-16T18:15:17.710 回答
2

对我来说,编译器不允许它:http ://codepad.org/KkzVCesh

于 2008-11-16T04:38:41.367 回答
1

你没有收到任何警告,因为你没有-Wall -Werror打开。(如其他答案所述)

但是我认为您可能得到了一个零填充的结构,因为堆栈对象是在调用者函数中默认构造的,可能带有显式零参数,或者由于堆栈上的零?

于 2008-11-16T09:58:00.100 回答