considering this code snippet
#include <iostream>
int foo() {
int a;
return a;
}
int main() {
auto &&ret = std::move(foo());
std::cout << ret;
}
compile with ASAN g -fsanitize=address -fno-omit-frame-pointer -g -std=c 17 main.cpp -o main
, run ./main
shows error
==57382==ERROR: AddressSanitizer: stack-use-after-scope on address 0x00016b3cb480 at pc 0x000104a37e68 bp 0x00016b3cb450 sp 0x00016b3cb448
READ of size 4 at 0x00016b3cb480 thread T0
#0 0x104a37e64 in main main.cpp:8
#1 0x104a75084 in start 0x200 (dyld:arm64e 0x5084)
But if I remove reference after auto, this piece of code can compile and run without an error given by ASAN. What I don't understand is that if std::move
returns a reference to the object it is given, then that object, which is a temporary created by foo()
in this example, will be destroyed after std::move
function call return, so whether it is bind to rvalue reference or assigned to a new value should be invalid, because that temporary is already destroyed after move operation, right? Then why ASAN doesn't give an error in second case.
---------------------update----------------
#include <iostream>
int foo() {
int a = 1;
return a;
}
int main() {
auto &&ret = std::move(foo());
std::cout << ret;
}
if I don't compile with ASAN, this code will actually run without an error, it doesn't trigger segmentation fault or anything, and print 1 which is the same as a
in foo
funtion. and from the answers below I learned that the temporary is destroyed after auto &&ret = std::move(foo());
right? Is this an undefined behavior?
CodePudding user response:
Assigning a prvalue (such as an int
) to an auto&&
variable will enable lifetime extension. The temporary will exist as long as your variable exists.
Passing a prvalue to std::move()
will convert the prvalue (int
) to an xvalue (int&&
). Lifetime extension no longer applies, and the temporary is destroyed before reaching std::cout << ret;
This is why the presence of your std::move
is triggering stack-use-after-scope. The move is preventing lifetime extension.
The lesson: you likely never want to pass a prvalue (a temporary) to std::move
.
CodePudding user response:
the temporary is only destroyed after the full expression. (after the initialization of ret
)
// 1. the temporary is created and bind to std::move parameter
// 2. std::move return reference to the temporary
// 3. it's *value* copied to ret
// 4. temporary is destoryed, but it'd not effect the copy (`ret`)
auto ret = std::move(foo());
// 1. the temporary is created and bind to std::move parameter
// 2. std::move return reference to the temporary
// 3. the *reference* is set to `ret`
// 4. temporary is destoryed, `ret` now contains dangling reference
auto&& ret = std::move(foo());
CodePudding user response:
First things first, the program has undefined behavior as a
is uninitialized and you're copying it as your function returns by value.
This applies to both of your cases. Note that even though in the first case a reference is bind, your function still return by value so there is a copy involved theoretically(according to the standard), which will use the uninitialized variable with indeterminate value.
Next, std::move(foo())
is an xvalue expression and so the lifetime extension does not apply. The temporary will be destroyed at the end of the full expression.
Also refer to Why should I always enable compiler warnings?.