下面的代码生成一个悬空引用,可以在编译器发出的警告中看到,并且在函数返回之前调用函数A
中g()
对象的析构函数。还可以在“使用堆栈”之后验证main()
中的返回引用是否具有垃圾,至少在调试版本中是这样。但我无法在发布版本中重现相同的行为。这是为什么?编译器在这里做了什么样的优化,给人的印象是引用r
是好的?
#include <iostream>
struct A{
A(int i) : i(i) { std::cout << "Ctor\n"; }
A(const A& a) { i = a.i; std::cout << "Copy ctor\n"; }
~A() { std::cout << "Dtor\n"; }
int i;
};
A& g(int i) { A x(i); return x; }
int main()
{
const A& r = g(1);
std::cout << "Using the stack\n";
std::cout << r.i << '\n'; // r.i has garbage in debug, but not in a release build.
}
PS。我会反对NRVO,因为该函数不返回A
对象。
编辑:回应Mark Tolonen。即使我在const A& r = g(1);
之后包含这些表达式,发布版本也不会在std::cout << r.i << '\n';
中显示垃圾
std::cout << "Using the stack ...................................................................................................................\n";
std::cout << "Using the stack ...................................................................................................................\n";
std::cout << "Using the stack ...................................................................................................................\n";
std::cout << "Using the stack ...................................................................................................................\n";
答案 0 :(得分:11)
这只是未定义的行为。你通过引用返回一个临时的,任何事情都可能发生。
A& g(int i) { A x(i); return x; }
是非法的。
调试版本可能会清除内存并导致错误,因为内存已被清除。
发布版本不会打扰。你支付你使用的费用,对吗?它只是保持记忆不变,但是它被操作系统标记为可回收。之后所有手套都关闭了。
这是VC ++编译器带来的(可以说是)好事。你会看到在调试版本中发生的所有事情都可以帮助你......好吧......调试得更好。未初始化的指针设置为某个特定值,以便您知道它未初始化,内存在delete
后清零,以便您知道它已被删除。这有助于更快地发现问题,因为在发布版本中,您可能仍然会看到内存,如果它没有被覆盖,或者访问未初始化的指针并让出现工作等等。不会看到其他情况,当时你会发现会造成很大的伤害并且很难诊断。
答案 1 :(得分:1)
以下是Visual Studio 2012 64位版本的速度优化(/ O2编译器切换)版本在运行此代码并打印出一个代码时实际执行的操作:
int main()
{
000000013F7C7E50 sub rsp,28h
const A& r = g(1);
000000013F7C7E54 lea rdx,[string "Ctor\n" (013F83DA4Ch)]
000000013F7C7E5B lea rcx,[std::cout (013F85FAA0h)]
000000013F7C7E62 call std::operator<<<std::char_traits<char> > (013F7C1500h)
000000013F7C7E67 lea rdx,[string "Dtor\n" (013F83DA54h)]
000000013F7C7E6E lea rcx,[std::cout (013F85FAA0h)]
000000013F7C7E75 call std::operator<<<std::char_traits<char> > (013F7C1500h)
std::cout << "Using the stack\n";
000000013F7C7E7A lea rdx,[string "Using the stack\n" (013F83DA60h)]
000000013F7C7E81 lea rcx,[std::cout (013F85FAA0h)]
000000013F7C7E88 call std::operator<<<std::char_traits<char> > (013F7C1500h)
std::cout << r.i << '\n'; // r.i has garbage in debug, but not in a release build.
000000013F7C7E8D lea rcx,[std::cout (013F85FAA0h)]
000000013F7C7E94 mov edx,1
000000013F7C7E99 call std::basic_ostream<char,std::char_traits<char> >::operator<< (013F7C1384h)
000000013F7C7E9E mov dl,0Ah
000000013F7C7EA0 mov rcx,rax
000000013F7C7EA3 call std::operator<<<std::char_traits<char> > (013F7C10EBh)
请注意,它甚至不会真正创建和销毁A
对象。它只需拨打cout
四次。每次rdx
保存要打印的对象。前三个打印字符串“Ctor \ n”,“Dtor \ n”和“使用堆栈\ n”。最后一个看起来只是打印edx
中的整数1
。
编译器可以为未定义的行为做任何事情。除了一个用于空间优化(/ O1编译器开关)或OP找到,未优化(/ Od)之外的其他东西。