2

下面是一个带标签的联合模板“Storage”的简化示例,它可以假设两种类型 L 和 R 包含在一个联合中,加上一个 bool 指示它们中的哪一个被存储。实例化使用两种不同大小的类型,较小的类型实际上是空的。

#include <utility>

struct Empty
{
};

struct Big
{
        long a;
        long b;
        long c;
};

template<typename L, typename R>
class Storage final
{
public:
        constexpr explicit Storage(const R& right) : payload{right}, isLeft{false}
        {
        }

private:
        union Payload
        {
                constexpr Payload(const R& right) : right{right}
                {
                }
                L left;
                R right;
        };

        Payload payload;
        bool isLeft;
};

// Toggle constexpr here
constexpr static Storage<Big, Empty> createStorage()
{
        return Storage<Big, Empty>{Empty{}};
}

Storage<Big, Empty> createStorage2()
{        
        return createStorage();
}
  • 构造函数用 Empty 初始化 R 成员,并且只为该成员调用联合的构造函数
  • 联合永远不会默认初始化为一个整体
  • 所有构造函数都是 constexpr

因此,函数“createStorage2”应该只填充 bool 标记,而不要理会联合。所以我期望一个带有默认优化“-O”的编译结果:

createStorage2():
        mov     rax, rdi
        mov     BYTE PTR [rdi+24], 0
        ret

GCC 和 ICC 都生成类似的东西

createStorage2():
        mov     rax, rdi
        mov     QWORD PTR [rdi], 0
        mov     QWORD PTR [rdi+8], 0
        mov     QWORD PTR [rdi+16], 0
        mov     QWORD PTR [rdi+24], 0
        ret

将整个 32 字节结构归零,而 clang 生成预期的代码。您可以使用https://godbolt.org/z/VsDQUu重现此内容。只有当您从“createStorage”静态函数中删除 constexpr 时,GCC 才会恢复到所需的 bool 标记初始化,而 ICC 保持不变并仍填充所有 32 个字节。

这样做可能不违反标准,因为“未定义”的未使用位允许任何事情,包括设置为零和消耗不必要的 CPU 周期。但是很烦人,如果您首先出于效率原因引入了工会,并且您的工会成员的规模差异很大。

这里发生了什么?如果从构造函数和静态函数中删除 constexpr 不是一种选择,是否有任何方法可以解决此问题?

附注:即使所有 constexpr 都被删除,ICC 似乎也会执行一些额外的操作,如https://godbolt.org/z/FnjoPC

createStorage2():
        mov       rax, rdi                                      #44.16
        mov       BYTE PTR [-16+rsp], 0                         #39.9
        movups    xmm0, XMMWORD PTR [-40+rsp]                   #44.16
        movups    xmm1, XMMWORD PTR [-24+rsp]                   #44.16
        movups    XMMWORD PTR [rdi], xmm0                       #44.16
        movups    XMMWORD PTR [16+rdi], xmm1                    #44.16
        ret                                                     #44.16

这些 movups 指令的目的是什么?

4

1 回答 1

0

(这只是我的猜测,但评论太长了)

这里发生了什么?

由于构造函数是constexpr,因此可能Payload整个 都有一些在编译时计算的值。然后,在运行时,Payload返回该完成。据我所知,编译器不需要识别编译时值的某个部分未初始化并且不应该为它生成任何代码。

在一些疯狂的编译器中,甚至可能发生编译时Payload在未初始化的部分中有垃圾值,然后它会产生例如:

createStorage2():
        mov     rax, rdi
        mov     QWORD PTR [rdi], 0xbaadf00d
        mov     QWORD PTR [rdi+8], 0xbaadf00d
        mov     QWORD PTR [rdi+16], 0xbaadf00d
        mov     QWORD PTR [rdi+24], 0
        ret

一般来说constexpr,不喜欢未初始化的值,但联合是一种解决方法。

于 2020-03-06T17:55:40.533 回答