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

Ily*_*sov 6 c++ optimization gcc x86-64 c++20

鉴于以下结构...

#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;
}
Run Code Online (Sandbox Code Playgroud)

...海湾合作委员会(在x86-64的Linux版本的10.2)已启用优化(在所有3个级别)会产生以下汇编[1]construct

construct():
        mov     r8, rdi
        xor     eax, eax
        mov     ecx, 32
        rep stosq
        mov     rax, r8
        ret
Run Code Online (Sandbox Code Playgroud)

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

#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;
}
Run Code Online (Sandbox Code Playgroud)

...生成的程序集更改为单独初始化每个字段,而不是原始中的单个 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
Run Code Online (Sandbox Code Playgroud)

显然,这两个结构在不是微不足道的方面是等效的,而是标准布局。只是 gcc 错过了一个优化机会,还是从 C++ 语言的角度来看还有更多?


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


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

mja*_*bse 2

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

也就是说,[隐式定义的构造函数]调用该类的基类和非静态成员的默认构造函数。如果这满足 constexpr 构造函数的要求,则生成的构造函数是 constexpr (C++11 起)。

因此,正如数组的初始化long是一样constexpr,隐式定义的构造函数也是如此。然而,用户定义的则不然,因为它没有标记constexpr。我们也可以通过尝试制作construct示例的功能来证实这一点constexpr。对于隐式定义的构造函数,这可以正常工作,但对于空的用户定义版本,它无法编译,因为

<source>:3:8:注意:“C”不是聚合,没有简单的默认构造函数,并且没有不是复制或移动构造函数的“constexpr”构造函数

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

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

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

    constexpr C() {}
};
Run Code Online (Sandbox Code Playgroud)

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

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


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

struct D {
    D() {}
};

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

    C() = default;
};
Run Code Online (Sandbox Code Playgroud)

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