1

Why do I get Segmentation fault with the code below?

#include <iostream>
#include <string>

const std::string& f() { return "abc"; }
std::string&& g() { return "xyz"; }

int main()
{
    const std::string& s1 = f();

    std::string&& s2 = g();

    s2 += "-uvw";

    std::cout << s1 << ", " << s2 << std::endl;

    return 0;
}

I expected that both s1 and s2 are still alive with I print them, but actually they are not. Why?

For example, they are alive in the code below that does not crash:

#include <iostream>
#include <string>

int main()
{
    const std::string& s1 = "abc";

    std::string&& s2 = "xyz";

    s2 += "-uvw";

    std::cout << s1 << ", " << s2 << std::endl;

    return 0;
}

what is the difference?

Alexey Starinsky
  • 3,699
  • 3
  • 21
  • 57
  • Why do you expect them to be valid? Both functions have to make a temporary `std::string` from a `char const*`. – Stephen Newell Jul 27 '22 at 20:50
  • @StephenNewell see updated post. `const std::string& s1 = "abc";` does not crash. – Alexey Starinsky Jul 27 '22 at 20:53
  • 2
    The lifetime of temporary objects can be extended by assigning them to a const lvalue reference or a rvalue reference, but the values returned from the functions are not temporary objects, but references to objects temporary objects that were no longer required to be kept alive after the functions returned. If you change the return types to `std::string`, both should work. Returning an rvalue refernce is almost always an issue... – fabian Jul 27 '22 at 20:58
  • 2
    The rule that allows references to extend the lifetime of a temporary applies only if the reference is being directly initialized with the temporary. It is not transitive. If you create a reference which extends the lifetime of a temporary, you cannot then initialize another reference using the first to extend the lifetime of the temporary. The initialization is fine, but expecting the lifetime of the temporary to extend is not. – François Andrieux Jul 27 '22 at 21:01

2 Answers2

3

Your second example with no extra function calls is well-defined due to the reference lifetime extension rules. Essentially, when a prvalue is immediately bound to a reference upon creation, the lifetime of the referenced object is extended to that of the reference.

In your first example, reference lifetime extension does not apply. The prvalues created in your functions' return statements are not immediately bound to the references in main (there are intermediate steps). The objects referenced by the references returned by f and g are local to those functions, and immediately go out of scope when the function returns.

Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
0

You are creating temporary string objects on the stack. Once the function runs, the stack space is freed and you are holding an invalid reference. In the second example, the string is still stored in valid memory since the function has not returned yet.

Francis Godinho
  • 195
  • 1
  • 9
  • Side note: [Why are the terms "automatic" and "dynamic" preferred over the terms "stack" and "heap" in C++ memory management?](https://stackoverflow.com/q/9181782/4581301) – user4581301 Jul 27 '22 at 21:40