【发布时间】:2020-06-19 11:23:23
【问题描述】:
下面是一个标记联合模板“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 指令的目的是什么?
【问题讨论】:
-
FWIW,clang 做你想做的事:godbolt.org/z/hZdGZn
-
如果你避免列表初始化会有什么不同吗?例如
Storage<Big, Empty>(Empty{})而不是Storage<Big, Empty>{Empty{}}? -
@NathanOliver:是的,也注意到了这一点。这一观察证实不需要额外的初始化开销。
-
@BenVoigt:不,它没有。
标签: c++ constexpr zero-initialization