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 输出相同的地址。
肯定是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;
};
需要移动(或地址不变的省略)以确保内部指针指向正确的整数。但是由于省略,指针指向不包含成员整数的内存。
在此处查看输出 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 或复制构造函数,我们将得到预期的结果(即相同的地址)。
我认为应该,因为它对正确性很重要。但是,我很惊讶地看到 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 输出相同的地址。
肯定是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;
};
需要移动(或地址不变的省略)以确保内部指针指向正确的整数。但是由于省略,指针指向不包含成员整数的内存。
在此处查看输出 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 或复制构造函数,我们将得到预期的结果(即相同的地址)。