为什么返回对函数本地值的引用而不是编译错误?

时间:2014-10-10 09:54:58

标签: c++ return-by-reference

以下代码调用未定义的行为。

int& foo()
{
  int bar = 1234;
  return bar;
}

g ++发出警告:

  

警告:引用本地变量'bar'返回[-Wreturn-local-addr]

clang ++ too:

  

警告:引用与本地变量关联的堆栈内存' bar'返回[-Wreturn-stack-address]

为什么这不是编译错误(忽略-Werror)?

是否存在将ref返回到本地var的情况?

编辑正如所指出的,规范要求这是可编译的。那么,为什么规范不禁止这样的代码?

6 个答案:

答案 0 :(得分:38)

我想说,要求这样做会使程序格式错误(也就是说,使其成为编译错误)会使标准复杂化,效果甚微。在诊断此类案件时,您必须准确说明标准,并且所有编制者都必须实施这些案例。

如果指定的太少,则不会太有用。并且编译器可能已经检查了这个以发出警告,并且真正的程序员无论如何都会编译-Wall_you_can_give_me -Werror

如果指定太多,编译器很难(或不可能)实现该标准。

考虑这个类(你只有标题和库):

class Foo
{
  int x;

public:
  int& getInteger();
};

这段代码:

int& bar()
{
  Foo f;
  return f.getInteger();
}

现在,是否应该制定标准以使其形成错误?可能不会,如果Foo实现如下:

#include "Foo.h"

int global;

int& Foo::getInteger()
{
  return global;
}

同时,它可以像这样实现:

#include "Foo.h"

int& Foo::getInteger()
{
  return x;
}

当然,这会给你一个悬空参考。

我的观点是编译器无法确切地知道返回引用是否正常,除了一些简单的情况(返回对函数范围自动变量或非引用类型的参数的引用)。我认为让它的标准复杂化是值得的。特别是大多数编制者已经警告过这是一个实施质量问题。

答案 1 :(得分:7)

出于同样的原因,C允许您返回指向已释放的内存块的指针。

根据语言规范有效。这是一个非常糟糕的想法(而且关闭无法保证工作)但它仍然有效,因为它不是被禁止的。

如果您要求为什么标准允许这样做,那可能是因为,当引用引用时,这就是它们的工作方式。标准的每次迭代都有一定的指导方针(例如最大限度地减少“破坏变更”的可能性,那些使现有格式良好的程序无效的标准),标准是用户和实施者之间的协议,毫无疑问比实施者更多的实施者在委员会: - )

将这个想法作为一个潜在的变化并看到ISO所说的可能是值得的,但我怀疑它会被认为是那些“突破性变化”之一,因此非常怀疑。

答案 2 :(得分:7)

另外,因为你可能想要获取当前的堆栈指针(无论你的特定实现是什么意思)。

此功能:

 void* get_stack_pointer (void) { int x; return &x; };

AFAIK,如果你不取消引用结果指针,它就不是未定义的行为。

比这个便携得多:

 void* get_stack_pointer (void) { 
    register void* sp asm ("%esp"); return sp; }

至于为什么你可能想要获得堆栈指针:好吧,有些情况下你有正当理由得到它:例如保守的Boehm garbage collector需要扫描堆栈(所以想要堆栈指针和堆栈底部。)

如果您返回了一个C ++引用,您只能使用&一元运算符获取其地址,那么获得这样的地址是IIUC合法的(恕我直言合法操作你可以做的。)

获取堆栈指针的另一个原因是获得任何堆,本地或静态数据的非NULL指针地址(您可以例如散列)。但是,您可以使用(void*)1(void*)-1来实现此目的。

所以编译器只对此发出警告。

我猜C ++编译器应该接受

int& get_sp_ref(void) { int x; return x; }

void show_sp(void) { 
   std::cout << (&(get_sp_ref())) << std::endl; }

答案 3 :(得分:3)

为了扩展前面的答案,ISO C ++标准没有捕获警告和错误之间的区别;它只是使用术语“诊断”&#39;当提到编译器在看到格式错误的程序时必须发出的内容。引用N3337,1.4,第1和第2段:

  

可诊断规则集包含此中的所有语法和语义规则   国际标准除了那些包含明确表示“no   诊断是必需的“或被描述为导致”未定义的行为“。

     

虽然本国际标准仅规定了对C ++实现的要求,   如果这些要求被表达为要求,那么这些要求通常更容易理解   程序,程序的一部分或程序的执行。这样的要求有   以下含义:

     
      
  • 如果某个程序不违反本国际标准中的规则,a   符合要求的实施应在其资源限度内接受并正确执行   该计划。

  •   
  • 如果某个程序包含违反任何可诊断规则或出现的错误   本标准中描述的构造为“有条件支持”时的   实现不支持该构造,符合实现应发布   至少有一条诊断信息。

  •   
  • 如果程序包含违反不需要诊断的规则,则执行此操作   国际标准对此没有实施要求   程序

  •   

答案 4 :(得分:1)

其他答案中没有提到的是,如果永远不调用该函数,则此代码是正常的。

不需要编译器来诊断是否可能调用某个函数。例如,您可以设置一个程序来查找Fermat最后定理的反例,并在找到一个函数时调用该函数。编译器拒绝这样的程序是错误的。

答案 5 :(得分:0)

将引用返回到局部变量是个坏主意,但有些人可能会创建需要的代码,因此编译器应该只对此进行警告,并且不要将有效(有效结构)代码确定为错误。

带有 local 变量的

Angew already posted示例实际上是全局变量。然而,还有其他一些(恕我直言更好)的样本。

Object& GetSmth()
{
    Object* obj = new Object();
    return *obj;
}

在这种情况下,对本地对象的引用是有效的,并且在使用后调用者应该处理内存。


重要提示我不鼓励也不建议使用这种编码风格,因为它不好,通常很难理解发生了什么它会导致内存泄漏或崩溃等问题。这只是一个示例,说明为什么这种特殊情况不能被视为错误。