Iam*_*mIC 2 c++ clang strict-aliasing compiler-optimization type-punning
我有以下示例代码:
inline float successor(float f, bool const check)
{
const unsigned long int mask = 0x7f800000U;
unsigned long int i = *(unsigned long int*)&f;
if (check)
{
if ((i & mask) == mask)
return f;
}
i++;
return *(float*)&i;
}
float next1(float a)
{
return successor(a, true);
}
float next2(float a)
{
return successor(a, false);
}
Run Code Online (Sandbox Code Playgroud)
在 下x86-64 clang 13.0.1,代码按预期编译。
在 or 15下x86-64 clang 14.0.0,输出仅仅是andret的运算。next1(float)next2(float)
编译器选项:-march=x86-64-v3 -O3
代码和输出在这里:Godbolt。
该successor(float,bool)函数不是空操作。
请注意,输出符合 GCC、ICC 和 MSVCC 下的预期。我在这里错过了什么吗?
use*_*522 14
*(unsigned long int*)&f是立即的别名违规。f是一个float. 不允许您通过指向 的指针访问它unsigned long int。(这同样适用于*(float*)&i。)
因此,代码具有未定义的行为,并且 Clang 喜欢假设具有未定义行为的代码是不可访问的。
编译以-fno-strict-aliasing强制 Clang 不将别名冲突视为不可能发生的未定义行为(尽管这可能还不够,请参见下文),或者最好不要依赖未定义行为。而是使用std::bit_cast(C++20 起) 或创建具有新类型但具有相同对象表示形式的std::memcpy副本。f这样您的程序将是有效的标准 C++,而不依赖于-fno-strict-aliasing编译器扩展。
(如果您使用std::memcpyadd astatic_assert来验证这一点unsigned long int并float具有相同的大小。这在所有平台上都不是这样,也不是在所有常见平台上都是如此。std::bit_cast内置了测试。)
正如 @CarstenS 在另一个答案中注意到的,考虑到您(至少在编译器资源管理器上)正在为 SysV ABI 进行编译, (64 位)确实与(32 位)unsigned long int大小不同。float因此,存在更直接的 UB,因为您在 的初始化中访问内存越界i。他还注意到,当使用匹配大小的整数类型时,即使没有-fno-strict-aliasing. 不过,这并不会使我上面写的内容无效。