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

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

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

g ++发出警告:

警告:对返回的局部variables“bar”的引用[-Wreturn-local-addr]

铿锵++也是:

警告:返回与本地variables“bar”相关联的堆栈内存的引用[-Wreturn-stack-address]

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

有没有情况下返回一个本地变种的ref是有效的?

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

我要说的是,要求这个程序不合格(也就是说,这是一个编译错误)会使标准复杂化很less,没有什么好处。 当这些病例被诊断出来时,你必须准确地说出标准,所有的编译者都必须执行它们。

如果您指定的太less,那就不会太有用。 编译器可能已经检查了这个发出警告,真正的程序员用-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; } 

这当然会给你一个悬而未决的参考。

我的观点是编译器不能真正知道返回引用是否正确,除了一些微不足道的情况(返回对函数作用域自动variables或非引用types参数的引用)。 我认为不值得让这个标准复杂化。 特别是由于大多数编译器已经警告过这个问题,因为这是一个质量问题。

此外,因为你可能想获得当前的堆栈指针(无论你的具体实现是什么意思)。

这个function:

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

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

比这个更便携:

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

至于为什么你可能想得到堆栈指针:好吧,有些情况下你有一个合理的理由来得到它:例如保守的Boehm垃圾回收器需要扫描堆栈(所以想要堆栈指针和堆栈底部) 。

如果你返回了一个C ++引用,你只能使用&一元运算符来获取这个地址,那么这个地址是IIUC合法的(这是IMHO 唯一可以做的合法操作)。

获取堆栈指针的另一个原因是得到一个非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; } 

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

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

如果你问为什么这个标准允许,这可能是因为,当引用引用时,这是他们的工作方式。 标准的每一次迭代都有一定的指导原则(比如最大限度地减less“突变”的可能性,使得现有格式良好的程序无效),这个标准是用户和实施者之间的协议,毫无疑问比用户更多的实现者在委员会:-)

把这个想法作为一个潜在的改变,看看ISO的说法是值得的,但是我怀疑它会被认为是“突变”之一,因此是非常可疑的。

为了扩展早期的答案,ISO C ++标准并没有把警告和错误区分开来 当提到编译器在看到不合格的程序时必须发出什么内容时,它简单地使用术语“诊断”。 引用N3337,1.4第1和2段:

一套可诊断的规则由本国际标准中的所有句法和语义规则组成,除了那些包含“不需要诊断”或被描述为导致“未定义的行为”的明确标记的规则。

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

  • 如果一个程序没有违反本标准中的规则,那么一致性实施应在其资源限制内接受并正确执行该程序。

  • 如果程序包含违反任何可诊断规则或本标准中描述的构造发生的情况,当实现不支持该构造时,符合的实现应至less发出一条诊断消息。

  • 如果一个程序违反了不需要诊断的规则,本国际标准没有要求对该程序的实施。

其他答案没有提到的东西是,如果函数从未被调用,这个代码是OK的。

编译器不需要诊断一个函数是否可以被调用。 例如,你可以build立一个程序,寻找费马最后定理的反例,如果find一个,就调用这个函数。 编译器拒绝这样的程序是错误的。

返回引用局部variables是不好的想法,但是有些人可能会创build需要的代码,所以编译器只应该警告,不要将有效的(有效结构)代码确定为错误的。

Angew已经发布了实际上是全局的局部variables的示例。 但是还有一些(恕我直言更好)的样本。

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

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


重要提示我不鼓励也不推荐使用这种编码风格,因为它不好 ,通常很难理解正在发生的事情,并导致某些问题,如内存泄漏或崩溃。 这只是一个示例,说明为什么这个特定的情况不能被视为错误。

Interesting Posts