为何GCC针对该std::bit_cast生成冗余代码?
我在使用std::bit_cast将自定义强类型数组转换为底层类型时,发现即使在高优化级别下,GCC仍会生成额外代码。示例代码如下:
#include <array> #include <bit> struct StrongType { float value; }; std::array<float, 2> bitCast(std::array<StrongType, 2> const a) { return std::bit_cast<std::array<float, 2>>(a); } std::array<float, 3> bitCast(std::array<StrongType, 3> const a) { return std::bit_cast<std::array<float, 3>>(a); }
使用-std=c++20 -O3编译时,Clang 18.1.0对两个重载均仅生成ret指令,但GCC 14.1针对三个元素的数组重载生成如下汇编代码:
bitCast(std::array<StrongType, 3ul>): movd DWORD PTR [rsp-16], xmm1 movss xmm1, DWORD PTR [rsp-16] movss DWORD PTR [rsp-32], xmm1 movd xmm1, DWORD PTR [rsp-32] ret
问题1:GCC为何生成这些代码?
这是GCC优化器在处理特定大小std::array位转换时的疏漏。对于2元素数组,GCC能识别出std::array<StrongType,2>与std::array<float,2>的内存布局完全一致(都是连续的两个float大小的内存块),可以直接通过调用约定的寄存器传递完成转换,无需额外操作;但对于3元素数组,GCC未完成彻底优化,错误生成了冗余的栈存储-加载操作。本质上这是GCC在处理非SIMD友好大小(3个float并非XMM寄存器的整数倍)的类型位转换时的局限性,没意识到这种转换完全可以零开销完成。
问题2:std::bit_cast是否是执行此类无未定义行为转换的最简方式?
是的。std::bit_cast是C++20标准提供的专门用于按位类型转换的工具,它在编译期确保源类型和目标类型大小相同,是安全且无未定义行为的转换方式。相比传统的reinterpret_cast(需额外指针转换,易触发未定义行为)或手动memcpy(有运行时开销且代码繁琐),std::bit_cast是语法最简洁、语义最明确且符合标准的选择。
问题3:未来此类场景下std::bit_cast能否实现零开销?
完全有可能。这种情况属于GCC优化器的可改进点,Clang已经实现了对应的零开销优化,说明这种优化是可行的。随着GCC对C++20特性优化的逐步完善,后续版本的GCC应该会修复这个优化疏漏,针对这类内存布局完全兼容的类型转换,生成无额外代码的ret指令。
内容的提问来源于stack exchange,提问作者Bernardo Sulzbach

