11

I think it should, because it's important for correctness. However, I'm surprised to see Clang's output. Consider the code below:

#include <iostream>

struct S
{
    int i;

    S(int i) : i(i) {}

    S(S&&)
    {
        std::cout << "S(S&&)\n";
    }

    S(S const&) = delete;
};

S f()
{
    S s{42};
    std::cout << &s << "\n";
    return s;
}

int main()
{
    S s{f()};
    std::cout << &s << "\n";
    std::cout << s.i << "\n";
}

We defined a move ctor for S to check if S(S&&) is called, if not, NRVO is applied.

The result we see from GCC is:

0x7ffc3ed7b5ac
0x7ffc3ed7b5ac
42

NRVO is applied and they take the same address, which is expected.

However, Clang's output:

0x7fff908bbcc8
0x7fff908bbcf8
42

NRVO is applied but the addresses differ.

In case you wonder why having the same address is important - it's because some object may do some registration with its address at construction, and if the object is moved, it should be notified (e.g. via move-ctor).

Having NRVO applied but with different memory address thus makes it ill-formed. It's a clear violation of the contract - no custom move/copy ctor is called, how could the compiler "copy" the data of S to a different place?

Is this a bug in Clang?


If we add a destructor to S, e.g.

~S() {}

This time, Clang outputs the same address.

Rakete1111
  • 47,013
  • 16
  • 123
  • 162
Jamboree
  • 5,139
  • 2
  • 16
  • 36
  • @Curious Still [different](https://wandbox.org/permlink/GZlS9m7giuF7mFvi). – Jamboree Jul 09 '17 at 15:10
  • Answered the question with that thought, they really should be the same – Curious Jul 09 '17 at 15:16
  • 1
    See the chain of emails starting at http://sourcerytools.com/pipermail/cxx-abi-dev/2016-February/002884.html. This is a bug in the Itanium ABI specification that Clang implemented to the letter. – T.C. Jul 09 '17 at 19:40

1 Answers1

5

Definitely seems to be a bug in clang, they should be the same, else things like the following will be erroneous

struct S
{
    int i;
    int* ptr;

    S(int i) : i(i) {
        this->ptr = &this->i;
    }

    S(S&& s)
    {
        this->i = s.i; 
        this->ptr = &this->i;
        std::cout << "S(S&&)\n";
    }

    S(S const&) = delete;
};

Where a move (or elision where addresses don't change) is required to ensure that the internal pointer points to the correct integer. But because of elision that pointer points to memory that does not contain the member integer.

See output here https://wandbox.org/permlink/NgNR0mupCfnnmlhK

As pointed out by @T.C., this is actually a bug in the Itanium ABI spec that doesn't take move-ctor into account. Quoting from Clang's dev:

Clang's rule is the one in the ABI: a class is passed indirectly if it has a non-trivial destructor or a non-trivial copy constructor. This rule definitely needs some adjustment [...]

Indeed, if we define either a non-trivial dtor or copy-ctor for S in the original example, we get the expected result (i.e. same address).

Curious
  • 20,870
  • 8
  • 61
  • 146
  • 1
    Yes. And it only does this when `sizeof(S)<=16`, so `S` fits into 2 registers. It returns s with registers (so no NRVO used). The compiled code cannot be correct, as `f()` has no input parameter for the address of `s` of `main`. – geza Jul 09 '17 at 16:34
  • OK, ABI passing rules tie this "NRVO vs. copying/moving" behavior to non-trivial destructor... But when the decision has already been made in favor of copying/moving (re: Clang), which ABI rule allows copying without invoking any constructor? What copying mechanism is used by Clang in this case? What does ABI say about this? – AnT stands with Russia Jul 10 '17 at 02:27