The code below generates a dangling reference as can be seen in the warning emitted by the compiler and the fact that the destructor for the A
object in function g()
is invoked before the function returns. One can also verify that in main()
after "using the stack", the returned reference has garbage, at least in a debug build. But I couldn't reproduce the same behavior in a release build. Why is that? What kind of optimization the compiler is doing here to give the impression that the reference r
is Ok?
#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. I would argue against NRVO, as the function doesn't return an A
object.
Edit: In response to Mark Tolonen. Even if I include these expressions after const A& r = g(1);
the release build doesn't show garbage in 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";
It's just undefined behavior. You return a temporary by reference, anything can happen.
The A& g(int i) { A x(i); return x; }
is illegal.
A debug build will probably clear the memory and cause errors because the memory was cleared.
A release build doesn't bother. You pay for what you use, right? It just leaves the memory untouched, but marks it as reclaimable by the OS. All gloves are off afterwards.
It's an (arguably) good thing that comes with the VC++ compiler. You'll see all sort of stuff happening in debug build to help you... well... debug better. Uninitialized pointers set to some specific value so that you know it's uninitialized, memory zeroed out after a delete
so that you know it was deleted. This helps in identifying problems sooner, because in a release build you'd probably still see the memory if it wasn't overwritten, or access an uninitialized pointer and have it appear to work, etc. Problems you wouldn't see otherwise, and at the time you would spot would cause a lot of harm and would be very hard to diagnose.
Here's what the speed-optimized (/O2 compiler switch) release build of Visual Studio 2012 64-bit actually does when it runs this code and prints out a one:
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)
Note that it doesn't even bother to really create and destroy the A
object. All it does is call cout
four times. Each time, rdx
holds the object to print. The first three print the strings "Ctor\n", "Dtor\n", and "Using the stack\n". The last one looks like it just prints the integer in edx
which is a 1
.
The compiler can really do anything for undefined behavior. It prints something besides a one for space-optimized (/O1 compiler switch), or as the OP found, not-optimized (/Od).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With