首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >RVO失败时的力编译错误

RVO失败时的力编译错误
EN

Stack Overflow用户
提问于 2013-10-09 02:27:58
回答 2查看 780关注 0票数 9

这里有很多关于什么时候可以做RVO的讨论,但是关于什么时候实际做的讨论不多。如前所述,RVO不能按照标准得到保证,但是是否有一种方法可以保证RVO优化成功或者相应的代码无法编译?

到目前为止,当RVO失败时,我部分地成功地使代码发出链接错误。为此,我声明副本构造函数,而不定义它们。显然,在我需要实现一个或两个复制构造函数(即x(x&&)x(x const&) )的情况下,这既不健壮,也不可行。

这就引出了我的第二个问题:当用户定义的复制构造函数到位时,为什么编译器编写器选择启用RVO?

第三个问题:是否有其他方法为普通数据结构启用RVO?

最后一个问题(承诺):,您知道有什么编译器可以使我的测试代码在gcc和clang的情况下表现不同吗?

下面是gcc 4.6、gcc 4.8和clang 3.3的一些示例代码,说明了问题所在。此行为不依赖于一般优化或调试设置。当然,选项--no-elide-constructors按它说的做,即关闭RVO。

代码语言:javascript
复制
#include <iostream>
using namespace std;

struct x
{
    x () { cout << "original x address" << this << endl; }
};
x make_x ()
{
    return x();
}

struct y
{
    y () { cout << "original y address" << this << endl; }
    // Any of the next two constructors will enable RVO even if only
    // declared but not defined. Default constructors will not do!
    y(y const & rhs);
    y(y && rhs);
};
y make_y ()
{
    return y();
}

int main ()
{
    auto x1 = make_x();
    cout << "copy of  x address" << &x1 << endl;
    auto y1 = make_y();
    cout << "copy of  y address" << &y1 << endl;
}

输出:

代码语言:javascript
复制
original x address0x7fff8ef01dff
copy of  x address0x7fff8ef01e2e
original y address0x7fff8ef01e2f
copy of  y address0x7fff8ef01e2f

RVO似乎也不适用于普通的数据结构:

代码语言:javascript
复制
#include <iostream>

using namespace std;

struct x
{
    int a;
};

x make_x ()
{
    x tmp;
    cout << "original x address" << &tmp << endl;
    return tmp;
}

int main ()
{
    auto x1 = make_x();
    cout << "copy of  x address" << &x1 << endl;
}

输出:

代码语言:javascript
复制
original x address0x7fffe7bb2320
copy of  x address0x7fffe7bb2350

更新:注意到某些优化很容易与RVO混淆。像make_x这样的构造函数帮助程序就是一个例子。参见这个例子,其中的优化实际上是由标准强制执行的。

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2013-10-09 11:56:38

问题是编译器做了太多的优化:)

首先,我禁用了make_x()的内联,否则我们无法区分RVO和内联。但是,我确实将其余部分放入了一个匿名命名空间中,这样外部链接就不会干扰任何其他编译器优化。(如证据所示,外部联系可以防止内联,谁知道还有什么.)我重写了输入输出,现在它使用了printf();否则生成的程序集代码会因为所有的iostream内容而变得杂乱无章。所以密码:

代码语言:javascript
复制
#include <cstdio>
using namespace std;

namespace {

struct x {
    //int dummy[1024];
    x() { printf("original x address %p\n", this); }
};

__attribute__((noinline)) x make_x() {
    return x();
}

} // namespace

int main() {
    auto x1 = make_x();
    printf("copy  of x address %p\n", &x1);
}

我和我的同事一起分析了生成的汇编代码,因为我对gcc生成的程序集的理解非常有限。今天晚些时候,我在-S -emit-llvm标志中使用clang来生成LLVM组件,我个人认为它比X86组装/气体合成更好、更容易阅读。使用哪个编译器并不重要,结论是一样的。

我在C++中重写了生成的程序集,如果x为空,则大致如下所示:

代码语言:javascript
复制
#include <cstdio>
using namespace std;

struct x { };

void make_x() {
    x tmp;
    printf("original x address %p\n", &tmp);
}

int main() {
    x x1;
    make_x();
    printf("copy  of x address %p\n", &x1);
}

如果x很大( int dummy[1024];成员未注释):

代码语言:javascript
复制
#include <cstdio>
using namespace std;

struct x { int dummy[1024]; };

void make_x(x* x1) {

    printf("original x address %p\n", x1);
}

int main() {
    x x1;
    make_x(&x1);
    printf("copy  of x address %p\n", &x1);
}

结果表明,如果对象是空的,make_x()只需要打印一些有效的唯一地址。如果对象是空的,make_x()可以自由地打印一些有效地址,指向它自己的堆栈。也没有什么要复制的,也没有什么可以从make_x()返回的。

如果您使对象更大(例如,添加int dummy[1024];成员),它就会被构造到位,这样RVO就会启动,并且只有对象的地址被传递给make_x()来打印。没有对象会被复制,什么都不会被移动。

如果对象是空的,编译器可以决定不将一个地址传递给make_x() (这会浪费多少资源?:),但是让make_x()从自己的堆栈中组成一个唯一的、有效的地址。当这种优化发生时,有些模糊,很难推理(这就是您在y中看到的),但这并不重要。

在那些重要的情况下,RVO似乎会持续发生。而且,正如我先前的混淆所示,即使是整个make_x()函数也可以内联,因此首先不需要对返回值进行优化。

票数 5
EN

Stack Overflow用户

发布于 2013-10-09 15:17:10

  1. 我不相信有什么办法能保证这样做。RVO是一种优化,因此编译器可以在特定情况下确定使用RVO实际上是一种去优化,并选择不使用RVO。
  2. 我假设您指的是您的第一个代码片段。在32位编译中,即使没有启用优化,我也无法在g++ 4.4、4.5或4.8 (通过ideone.com)上重现您的断言。在64位编译中,我可以再现您的非RVO行为。这闻起来像是g++中的64位代码生成错误。
  3. 实际上,如果我在(2)中观察到的是一个bug,那么一旦修复了该bug,它就会正常工作。
  4. 我可以确认,Sun也没有RVO你的具体例子,即使在32位编译。

但是,我确实想知道,您打印地址的内省代码是否导致编译器抑制优化(例如,它可能需要抑制优化以防止可能的混叠问题)。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/19262009

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档