NRVO不应该保证本地命名变量和call-site变量采用相同的地址吗?

Jam*_*ree 11 c++ nrvo

我认为它应该,因为它对正确性很重要.但是,我很惊讶地看到了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";
}
Run Code Online (Sandbox Code Playgroud)

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

我们从GCC看到的结果是:

0x7ffc3ed7b5ac
0x7ffc3ed7b5ac
42
Run Code Online (Sandbox Code Playgroud)

应用NRVO并且它们采用相同的地址,这是预期的.

但是,Clang的输出:

0x7fff908bbcc8
0x7fff908bbcf8
42
Run Code Online (Sandbox Code Playgroud)

应用NRVO但地址不同.

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

应用NRVO但具有不同的存储器地址因此使其形成不良.这明显违反了合同 - 没有调用自定义移动/复制ctor,编译器如何将S的数据"复制"到另一个地方?

这是Clang的一个错误吗?


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

~S() {}
Run Code Online (Sandbox Code Playgroud)

这次,Clang输出相同的地址.

Cur*_*ous 5

肯定似乎是clang中的一个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;
};
Run Code Online (Sandbox Code Playgroud)

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

请参阅此处的输出https://wandbox.org/permlink/NgNR0mupCfnnmlhK

正如@TC所指出的,这实际上是Itanium ABI规范中的一个错误,它不考虑move-ctor.引自Clang的开发:

Clang的规则是ABI中的规则:如果一个类具有非平凡的析构函数或非平凡的复制构造函数,则会间接传递它.这个规则肯定需要一些调整[...]

实际上,如果我们S在原始示例中定义了一个非平凡的dtor或copy-ctor ,我们就会得到预期的结果(即相同的地址).