空默认构造函数与隐式定义构造函数的不同机器代码

Different machine code for empty default constructor v. implicitly-defined one

给定以下结构...

#include <type_traits>

struct C {
    long a[16]{};
    long b[16]{};

    C() = default;
};

// For godbolt
C construct() {
    static_assert(not std::is_trivial_v<C>);
    static_assert(std::is_standard_layout_v<C>);

    C c;
    return c;
}

...gcc(x86-64 Linux 上的版本 10.2)启用优化(在所有 3 个级别)为 construct:

construct():
        mov     r8, rdi
        xor     eax, eax
        mov     ecx, 32
        rep stosq
        mov     rax, r8
        ret

一旦我提供了空的默认构造函数...

#include <type_traits>

struct C {
    long a[16]{};
    long b[16]{};

    C() {}  // <-- The only change
};

// For godbolt
C construct() {
    static_assert(not std::is_trivial_v<C>);
    static_assert(std::is_standard_layout_v<C>);

    C c;
    return c;
}

...生成的程序集更改为单独初始化每个字段而不是原始中的单个 memset:

construct():
        mov     rdx, rdi
        mov     eax, 0
        mov     ecx, 16
        rep stosq
        lea     rdi, [rdx+128]
        mov     ecx, 16
        rep stosq
        mov     rax, rdx
        ret

显然,就不是微不足道而是标准布局而言,这两个结构是等价的。 只是 gcc 缺少优化机会,还是从 C++ 语言的角度来看还有更多优化机会?


该示例是生产代码的精简版本,在性能上确实存在 material 差异。


[1] 神箭:https://godbolt.org/z/8n1Mae

虽然我同意这似乎是一个错失的优化机会,但我从语言级别的角度注意到了一个差异。隐式定义的构造函数是 constexpr 而您示例中的空默认构造函数不是。来自 cppreference.com:

That is, [the implicitly-defined constructor] calls the default constructors of the bases and of the non-static members of this class. If this satisfies the requirements of a constexpr constructor, the generated constructor is constexpr (since C++11).

所以long数组的初始化是constexpr,隐式定义的构造函数也是。但是,用户定义的不是,因为它没有标记 constexpr。我们还可以通过尝试使示例 constexprconstruct 函数来确认这一点。对于隐式定义的构造函数,这没有任何问题,但对于空的用户定义版本,它无法编译,因为

<source>:3:8: note: 'C' is not an aggregate, does not have a trivial default constructor, and has no 'constexpr' constructor that is not a copy or move constructor

正如我们在这里看到的:https://godbolt.org/z/MnsbzKv1v

因此,为了解决这个差异,我们可以创建空的用户定义构造函数 constexpr:

struct C {
    long a[16]{};
    long b[16]{};

    constexpr C() {}
};

有点令人惊讶的是,gcc 现在生成了优化版本,即与默认构造函数完全相同的代码:https://godbolt.org/z/cchTnEhKW

我不知道为什么,但这种 constexprness 的差异实际上似乎在这种情况下有助于编译器。因此,虽然看起来 gcc 应该能够在不指定 constexpr 的情况下生成相同的代码,但我想知道它可能是有益的是件好事。


作为对此观察的附加测试,我们可以尝试使隐式定义的构造函数成为非 constexpr 并查看 gcc 是否无法进行优化。我能想到的一种尝试测试它的简单方法是让 C 从具有非 constexpr 默认构造函数的空 class 继承:

struct D {
    D() {}
};

struct C : D {
    long a[16]{};
    long b[16]{};

    C() = default;
};

事实上,这会生成再次单独初始化字段的程序集。一旦我们使 D() constexpr,我们就得到了优化后的代码。参见 https://godbolt.org/z/esYhc1cfW