4

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?

9
  • 3
    This is undefined behavior as a is uninitialized. Enable all warnings. Commented Sep 27, 2022 at 16:10
  • std::move doesnt actually move. Commented Sep 27, 2022 at 16:12
  • T&& can bind to temporary and prolong the lifetime. but not the case when it's already reference. EDIT: I see, you're not talking about remove std::move but auto&& -> auto Commented Sep 27, 2022 at 16:12
  • Also, std::move doesn't actually move anything Commented Sep 27, 2022 at 16:13
  • 1
    The temporary is not destroyed until the full expression is finished, ie at the ;, after the reference is bound. Commented Sep 27, 2022 at 16:13

3 Answers 3

9

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.

Sign up to request clarification or add additional context in comments.

4 Comments

OP is not asking about remove std::move (which I also take wrong at first)
Thanks, i understand it now, temporary is destroyed at the end of full expression, and lifetime extension not applied for xvalue
but if I don't compile with ASAN, the code with "auto &&ret = std::move(foo());" can actually run without error, doesn't trigger segmentation fault or anything, and printed value is the same as a in foo funtion (i assigned a value to a). Is this an undefined behavior?
@codesavesworld Yes. It is a dangling reference and UB.
6

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()); 

Comments

3

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?.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.