NRVO 不应该保证本地命名变量和调用站点变量采用相同的地址吗?

Shouldn't NRVO guarantee the local named variable and the call-site variable to take the same address?

我认为应该,因为它对正确性很重要。但是,我很惊讶地看到 Clang 的输出。考虑以下代码:

#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";
}

我们为 S 定义了一个 move ctor 来检查 S(S&&) 是否被调用,如果没有,则应用 NRVO。

我们从 GCC 看到的结果是:

0x7ffc3ed7b5ac
0x7ffc3ed7b5ac
42

应用了 NRVO,它们采用相同的地址,这是预期的。

但是,Clang 的 output:

0x7fff908bbcc8
0x7fff908bbcf8
42

应用了 NRVO,但地址不同。

如果您想知道为什么拥有相同的地址很重要 - 这是因为某些对象可能会在构造时使用其地址进行一些注册,并且如果该对象被移动,则应该通知它(例如通过 move-ctor)。

已应用 NRVO 但具有不同的内存地址因此使其格式错误。 这明显违反了约定——没有自定义 move/copy ctor 被调用,编译器怎么会 "copy" 将 S 的数据转移到不同的地方?

这是 Clang 中的错误吗?


如果我们向 S 添加析构函数,例如

~S() {}

This time, Clang 输出相同的地址。

肯定是cla​​ng的bug,应该是一样的,不然会出现下面这样的错误

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;
};

需要移动(或地址不变的省略)以确保内部指针指向正确的整数。但是由于省略,指针指向不包含成员整数的内存。

在此处查看输出 https://wandbox.org/permlink/NgNR0mupCfnnmlhK

正如@T.C 所指出的,这实际上是 Itanium ABI 规范中的一个错误,它没有考虑到 move-ctor。引用自 Clang 的开发者:

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 [...]

事实上,如果我们在原始示例中为 S 定义一个非平凡的 dtor 或复制构造函数,我们将得到预期的结果(即相同的地址)。