除非返回值具有名称,否则为什么GCC无法优化?

时间:2019-06-13 10:10:19

标签: c++ gcc optimization g++

考虑以下代码:

#include <array>

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

C slow()
{
    return {};
}

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

GCC 6至9为slow()产生了非常肿的代码:

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错误提交:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90883

3 个答案:

答案 0 :(得分:1)

GCC维护者同意这是一个错误(缺少优化),并且已在x86_64的主干中修复(ARM可能会在以后修复):https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90883

答案 1 :(得分:0)

这并不是真正的完整答案,但可能会提供线索。我怀疑fastslow的含义之间存在细微差别,这可能会将编译器发送到不同的路径。如果将复制构造函数设为私有,则可以看到此信息。

https://godbolt.org/z/FMIRe3

#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;
}

即使带有复制省略号fast仍需要复制构造函数在slow返回initialization list的地方,该#include <array> class C { std::array<char, 7> a{}; public: C(){} private: C(const C & c){} }; C slow() { return {}; } C fast() { return C(); } 由调用者显式构造返回值。这些可能会或可能不会做同样的事情,但我相信编译器必须进行一些操作才能确定是否是这种情况。

有一篇详细的博客文章,提供了有关此主题的一些有趣背景

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

但是行为在C ++ 17中已更改

fast

return C()在C ++ 11下无法编译,而现在在C ++ 17下编译

https://godbolt.org/z/JG2PkD

原因是C fast(){ C c; return c; } 的含义从返回临时对象变为在调用者框架中显式构造对象。

所以现在在C ++ 17中,两者之间有很大的区别

C fast(){
    return C();
}

{{1}}

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

https://godbolt.org/z/i2eZnf

绝对不是C ++ 101

答案 2 :(得分:0)

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

对于slow

  • slow调用的pr值结果以{}作为初始值设定项(stmt.return)进行了复制初始化。
  • 因此,生成的对象被列表初始化([dcl.init]/17.1);
  • 这导致我们进行汇总初始化([dcl.init.list]/3.4

=>因此,调用slow的结果对象的所有成员都将使用其默认成员初始化程序dcl.init.aggr]/5.4进行初始化。

对于fast

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

进行初始化。

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

在慢速情况下,汇编仅次优。对象相应地返回到两个寄存器上的SystemV x86 abi:rax和rdx(edx)。首先,它将栈中位于地址[rsp-32]上类C的概念上的对象清零。将aab之间的填充字节清零。然后,它将已初始化的堆栈部分复制到寄存器中。将堆栈归零的方式只是次优的,所有这些操作等效于b程序集的2个xor操作。所以那只是一个明显的错误。