Search code examples
c++c++11move-semantics

C++ move semantics and function call


I'm refreshing my C++ skills and relearning move semantics. Here is the sample program and output which I can't make sense of.

#include <iostream>
#include <iomanip>

int sepI = 0;
void printSep(const char* msg) {
    std::cout
        << std::setw(40) << std::setfill('>') << "" << std::setfill(' ')
        << std::setw(2) << std::setiosflags(std::ios::right) << sepI++ << ": " << std::resetiosflags(std::ios::right)
        << std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
        << std::endl;
}

void printHook(const char* msg, void* p) {
    std::cout
        << std::setw(25) << std::setiosflags(std::ios::left) << msg << std::resetiosflags(std::ios::left)
        << std::setw(15) << std::setiosflags(std::ios::right) << p << std::resetiosflags(std::ios::right)
        << std::endl;
}

class MyT {
public:
    MyT() { printHook("constructor call", this); }
    MyT(MyT& t) { printHook("copy constructor call", this); }
    MyT(MyT&& t) { printHook("move constructor call", this); }

    ~MyT() { printHook("destructor call", this); v = 0; }

    MyT& operator=(MyT& t) { printHook("copy assigment call", this); return *this; }
    MyT& operator=(MyT&& t) { printHook("move assigment call", this); return *this; }

    int v = 1;
};

MyT fn() { return MyT(); }

int main() {
    printSep("a");
    MyT a;

    printSep("MyT&& v1 = std::move(a)");

    MyT&& v1 = std::move(a);
    std::cout << v1.v << " " << &v1 << std::endl;

    printSep("MyT&& v2 = MyT()");

    MyT&& v2 = MyT();
    std::cout << v2.v << " " << &v2 << std::endl;

    printSep("MyT&& v3 = std::move(MyT())");

    MyT&& v3 = std::move(MyT());
    std::cout << v3.v << " " << &v3 << std::endl;

    printSep("MyT&& v4 = fn()");

    MyT&& v4 = fn();
    std::cout << v4.v << " " << &v4 << std::endl;

    printSep("MyT&& v5 = std::move(fn())");

    MyT&& v5 = std::move(fn());
    std::cout << v5.v << " " << &v5 << std::endl;

    printSep("end");

    return 0;
}
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 0: a                            OK.
constructor call          0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 1: MyT&& v1 = std::move(a)      OK. Makes sense.
1 0x7ffe656eeae0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 2: MyT&& v2 = MyT()             OK. Makes sense.
constructor call          0x7ffe656eeae8
1 0x7ffe656eeae8
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 3: MyT&& v3 = std::move(MyT())  NOT OK.
constructor call          0x7ffe656eeaec
destructor call           0x7ffe656eeaec
1 0x7ffe656eeaec
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 4: MyT&& v4 = fn()              NOT OK.
constructor call          0x7ffe656eeaf0
1 0x7ffe656eeaf0
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5: MyT&& v5 = std::move(fn())   NOT OK.
constructor call          0x7ffe656eeaf4
destructor call           0x7ffe656eeaf4
1 0x7ffe656eeaf4
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 6: end
destructor call           0x7ffe656eeaf4
destructor call           0x7ffe656eeaf0
destructor call           0x7ffe656eeae8
destructor call           0x7ffe656eeae0

The questions are for the steps 3, 4, 5.

3
What's happening here?
Why isn't it the same as with step 2?
How is it possible that under the same address 0x7ffe656eeaec we called constructor - destructor - print chain and didn't get segfault?\

4 and 5
Where was the memory allocated from?
How did we get 2 different addresses of MyT instance out of non dynamic allocations in fn() calls?
Is it reserved static calls memory? Or they are allocated on stack then? Like compiler sees that those are local vars and reserves in program memory areas for such things. But I looked into disassembly and it was an ordinary function call. I couldn't check it on dynamic data, as I can't create rvalues array.\

It doesn't matter which optimization flags I set, as results are the same. But here is the build command:

g++ -std=c++17 -g -Wall -O3 -pedantic

Update 1: Reduced the code example.


Solution

    1. It's undefined behavior. You pass a temporary object as an argument to the parameter of std::move(), the parameter is a local variable of std::move(). The function returns a reference to a local variable, a returned reference to a local variable does not prolong an object life. This is undefined behavior.

    The question was edited. The original question asked about the 10th case. Now this is 3.

    1. Is OK. && prolongs life time of a temporary object.
    2. Is the same issue 3.