首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么GCC不能优化,除非返回值有一个名称?

为什么GCC不能优化,除非返回值有一个名称?
EN

Stack Overflow用户
提问于 2019-06-13 10:10:19
回答 3查看 322关注 0票数 9

请考虑以下代码:

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

class C
{
    std::array<char, 7> a{};
    int b{};
};

C slow()
{
    return {};
}

C fast()
{
    C c;
    return c;
}

GCC 6到9为slow()编写了非常臃肿的代码

代码语言:javascript
复制
slow():
        xor     eax, eax
        mov     DWORD PTR [rsp-25], 0
        mov     BYTE PTR [rsp-21], 0
        mov     edx, DWORD PTR [rsp-24]
        mov     DWORD PTR [rsp-32], 0
        mov     WORD PTR [rsp-28], ax
        mov     BYTE PTR [rsp-26], 0
        mov     rax, QWORD PTR [rsp-32]
        ret
fast():
        xor     eax, eax
        xor     edx, edx
        ret

这两种功能在意义上有区别吗?Clang为这两种语言发布类似fast()的代码,而GCC 4-5的代码比6-9做得更好,但也不是很理想。

构建标志:-std=c++11 -O3

演示:https://godbolt.org/z/rPNG9o

作为GCC错误提交,基于以下反馈:bug.cgi?id=90883

EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2019-07-11 06:40:13

GCC的维护人员一致认为这是一个错误(错过优化),并且修复了x86_64的主干(ARM可能稍后修复):bug.cgi?id=90883

票数 1
EN

Stack Overflow用户

发布于 2019-06-13 11:12:53

这并不是真正完整的答案,但它可能会给出一个线索。正如我所怀疑的,对于fastslow来说,它们的含义有细微的不同,这可能会将编译器发送到不同的路径。如果将复制构造函数设置为私有,则可以看到这一点。

https://godbolt.org/z/FMIRe3

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

class C
{
    std::array<char, 7> a{};

    public:
    C(){}

    private:
    C(const C & c){}
};

// Compiles
C slow()
{
    return {};
}

// Does not compile
C fast()
{
    C c;
    return c;
}

即使使用copy,fast仍然要求复制构造函数在那里,因为slow正在返回一个initialization list,该initialization list由调用方显式构造返回值。它们可能会也可能不会做同样的事情,但我相信编译器必须做一些处理来确定是否是这样的。

有一篇详细的博客文章给出了关于这个主题的一些有趣的背景。

https://akrzemi1.wordpress.com/2018/05/16/rvalues-redefined/

然而,C++17中的行为已经发生了变化。

鉴于

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

class C
{
    std::array<char, 7> a{};

    public:
    C(){}

    private:
    C(const C & c){}
};

C slow()
{
    return {};
}

C fast()
{
    return C();
}

fast将无法在C++11下编译--它现在在C++17下编译

https://godbolt.org/z/JG2PkD

原因是return C()的含义从返回临时到显式地在调用者的框架中构造对象。

所以现在在C++17中有一个很大的区别

代码语言:javascript
复制
C fast(){
    C c;
    return c;
}

代码语言:javascript
复制
C fast(){
    return C();
}

因为在第二个例子中,您甚至不需要一个副本或移动构造函数就可以使用。

https://godbolt.org/z/i2eZnf

绝对不是C++ 101

票数 1
EN

Stack Overflow用户

发布于 2019-06-13 16:16:07

这两个函数是等价的:返回的对象(更准确地说,是假设调用这些函数的结果对象)是通过使用其默认成员初始化器初始化每个成员来初始化的。

对于slow

  • 调用slow的pr值结果是以{}作为初始化器(stmt.return)进行复制初始化的。
  • 因此,得到的对象是列表初始化([dcl.init]/17.1);
  • 这导致了聚合-初始化([dcl.init.list]/3.4)

=>,因此调用slow的结果对象的所有成员都使用它们的默认成员初始化器dcl.init.aggr/5.4(http://eel.is/c++draft/dcl.init.aggr#5.4)初始化。

对于fast

  • 首先,我们假设复制省略是执行[class.copy.elision]/1.1的。
  • 因此函数调用的结果对象是默认初始化的[dcl.init]/12
  • 所以隐式声明默认构造函数被称为[dcl.init]/7.1
  • 此默认构造函数等效于C(){} ([class.default.ctor]/4)。

=>,因此调用slow的结果对象的所有成员都使用它们的默认成员初始化器[class.base.init]/9.1初始化。

这两个函数的结果程序集在功能上等效为。因此,Gcc生产的组件是符合标准的。

在缓慢的情况下,装配只是次优。该对象相应地返回到两个寄存器上的SystemV x86 abi : rax和rdx (edx)。首先,它将C类的对象概念归零到地址为rsp-32的堆栈上。它将aabb之间的填充字节归零。然后,它将堆栈的这个初始化部分复制到寄存器上。它将堆栈归零的方式只是次优,所有这些操作都等同于fast程序集的2 xor操作。所以这只是一个明显的错误。

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

https://stackoverflow.com/questions/56578196

复制
相关文章

相似问题

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