9

考虑这段代码:

#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

这两个函数的含义有区别吗?Clangfast()为两者都发出类似的代码,而 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

4

3 回答 3

1

这并不是真正的完整答案,但它可能会提供线索。正如我怀疑的那样,含义上存在细微的差异,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它由调用者显式构造返回值。这些可能会或可能不会最终做同样的事情,但我相信编译器必须做一些处理以确定是否是这种情况。

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

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

然而,行为在 C++17 中发生了变化

然而

#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 中有很大的区别

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

C fast(){
    return C();
}

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

https://godbolt.org/z/i2eZnf

绝对不是 C++ 101

于 2019-06-13T11:12:53.970 回答
1

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

对于 slow

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

对于fast

=> 所以调用的结果对象的所有成员slow都使用它们的默认成员初始化程序[class.base.init]/9.1进行初始化

两个函数生成的程序集在功能上是等效的。所以 Gcc 生产的程序集是符合标准的。

在缓慢的情况下,组装只是次优。该对象在两个寄存器上相应地返回到 SystemV x86 abi:rax 和 rdx (edx)。首先,它在地址 [rsp-32] 处将堆栈上的 C 类的对象概念归零。它将和和a之间的填充字节归零。然后它将堆栈的这个初始化部分复制到寄存器上。将堆栈归零的方式不是最理想的,无论如何,所有这些操作都等效于汇编的 2 个 xor 操作。所以这只是一个明显的错误。abbfast

于 2019-06-13T16:16:07.160 回答
1

GCC 维护人员同意这是一个错误(错过了优化),它已在 x86_64 的主干中修复(ARM 可能稍后会修复):https ://gcc.gnu.org/bugzilla/show_bug.cgi?id=90883

于 2019-07-11T06:40:13.397 回答