11

我认为它应该,因为它对正确性很重要。但是,我很惊讶地看到 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";
}

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

我们从 GCC 看到的结果是:

0x7ffc3ed7b5ac
0x7ffc3ed7b5ac
42

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

但是,Clang 的输出

0x7fff908bbcc8
0x7fff908bbcf8
42

应用了 NRVO,但地址不同。

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

应用了 NRVO 但具有不同的内存地址因此使其格式错误。这明显违反了合同 - 没有调用自定义移动/复制 ctor,编译器如何将 S 的数据“复制”到不同的地方?

这是 Clang 中的错误吗?


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

~S() {}

这一次,Clang 输出相同的地址。

4

1 回答 1

5

肯定是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

正如@TC 所指出的,这实际上是 Itanium ABI 规范中的一个错误,它没有考虑 move-ctor。引用 Clang 的开发人员的话:

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

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

于 2017-07-09T15:12:47.530 回答