#include <iostream>
class A {
public:
int x = 0;
};
A& a_returner() {
A a;
return a;
}
int main()
{
A& a = a_returner();
std::cout << a.x << std::endl;
}
在此代码中,a_returner()
返回对堆栈中变量a
的引用。如果我们这样打电话:
A a = a_returner();
这不是问题,因为我们只是复制返回的A
对象。但是,我们都在a_returner
中返回引用并绑定到引用。
问题是:为什么会这样?A
生活在a_returner
堆里.
编译器确实给出了
Warning: reference to local variable `a` returned
但是该程序成功地打印了0
.我认为从a_returner
返回的a
会访问无效数据。
你对"有效"的含义有一个错误的心理模型。
您在问题中的代码不起作用。当您尝试时它没有崩溃,结果是您所期望的,但这并不意味着(C++(一切都很好。
您刚刚发现的是"未定义行为"最糟糕的一面,即当您在C++中犯错误(例如返回对堆栈上对象的引用(时,任何事情都可能发生,包括什么都没有,代码显然可以按照您的意图工作。
代码仍然不起作用,并且在开发周期的后期会出现可怕的错误。
C++的主要哲学基础是程序员不犯错误,因此不是捕捉错误的"运行时错误角度",而是获得"未定义的行为守护进程",如果发生错误,程序可以做任何它想做的事情,包括暂时隐藏问题以便以后更努力地反咬。因此,在其他语言中,例如当您超出数组的边界时,您会得到运行时错误,而在C++中,您会得到未定义的行为,原因是假设C++程序员永远不会超出数组的边界,并且浪费时间检查这一点是没有意义的。
回顾一下:C++如果程序崩溃,您就知道代码有问题......如果程序没有崩溃并且结果是您所期望的,那么您仍然无法确定代码是否正确(即使是您作为输入提供的单个情况也是如此(。
许多C++学习者和一些自封C++"专家"认为崩溃是敌人,你应该防止崩溃(或者更糟糕的是吸收并吞下它们以继续运行(。实际上恰恰相反,崩溃是你的朋友,程序错误是敌人。问题是C++崩溃的频率不够高,有时应该崩溃的错误代码没有崩溃(如您的示例(。
在您的特定情况下,程序员有责任确保如果有引用,则引用的对象仍然处于活动状态,即存在引用的事实并不能保证该对象也存在,如果不是(如您的示例(,则代码是未定义的行为。 奇怪的是,C++有一个非常特殊的规则,在某些情况下,绑定引用的事实将"延长"应该立即丢弃的对象的寿命......例如:
int foo() {
return 42;
}
int bar() {
const int& x = foo(); // Note... a const reference
return x;
}
这段代码是有效且正确的,但这是一种意外,因为一个奇怪的规则说,如果将 const 引用绑定到临时引用,则临时生存期将延长到引用的生存期。 还要注意,这是一个非常微妙和精确的规则(例如,你必须理解法律术语,对于C++编译器来说什么是"临时的"(,除非你100%学习和理解该规则,否则你很有可能认为它适用,即使不适用。
只是不要那样做。
这是未定义的行为,一切皆有可能,即使它看起来运行良好。
顺便说一句:A a = a_returner();
也有未定义的行为。从a_returner()
返回的引用始终处于悬空状态,并且尝试从悬空引用初始化a
。
这在您的情况下(尽管是未定义的行为(有效,因为引用使用内存和别名的映射。(有关更多信息,请访问:C++指针和参考(。在您的情况下,函数a_returner()
中的对象a
正在使用的内存不会自动释放,因为操作系统可能不需要它。但是,正如@6502所提到的,该内存一直悬而未决。因此,该程序似乎有效。然而,正如@songyuanyao所解释的那样,这就是人们所说的未定义的行为。如果你仔细看看拆卸,你会发现所需的解释。