pla*_*cel 24 c++ strict-aliasing gcc-warning undefined-behavior type-punning
下面的代码通过一些位攻击执行快速反平方根操作.该算法可能是由Silicon Graphics在1990年代早期开发的,它也出现在Quake 3中. 更多信息
但是我从GCC C++编译器收到以下警告:解除引用类型惩罚指针将破坏严格别名规则
我应该使用static_cast
,reinterpret_cast
还是dynamic_cast
在这种情况下使用?
float InverseSquareRoot(float x)
{
float xhalf = 0.5f*x;
int32_t i = *(int32_t*)&x;
i = 0x5f3759df - (i>>1);
x = *(float*)&i;
x = x*(1.5f - xhalf*x*x);
return x;
}
Run Code Online (Sandbox Code Playgroud)
R. *_*des 36
忘记演员.使用memcpy
.
float xhalf = 0.5f*x;
uint32_t i;
assert(sizeof(x) == sizeof(i));
std::memcpy(&i, &x, sizeof(i));
i = 0x5f375a86 - (i>>1);
std::memcpy(&x, &i, sizeof(i));
x = x*(1.5f - xhalf*x*x);
return x;
Run Code Online (Sandbox Code Playgroud)
原始代码尝试int32_t
通过首先float
通过int32_t
指针访问对象来初始化,这是规则被破坏的地方.C风格的演员阵容相当于a reinterpret_cast
,因此改变它reinterpret_cast
不会产生太大的影响.
使用memcpy时的重要区别在于字节是从中复制float
到的int32_t
,但是float
对象永远不会通过int32_t
左值访问,因为memcpy
它指向void并且其内部是"神奇的"并且不会破坏别名规则.
Ste*_*non 12
这里有一些很好的答案可以解决打字问题.
我想解决"快速反平方根"部分.不要在现代处理器上使用这个"技巧".每个主流矢量ISA都有一个专用的硬件指令,为您提供快速的反平方根.它们中的每一个都比这个经常复制的小黑客更快更准确.
这些说明都可以通过内在函数获得,因此它们相对容易使用.在SSE中,你想使用rsqrtss
(intrinsic :) _mm_rsqrt_ss( )
; 在NEON中你想要使用vrsqrte
(内在:) vrsqrte_f32( )
; 在AltiVec中你想要使用frsqrte
.大多数GPU ISA具有类似的指令.可以使用相同的牛顿迭代来细化这些估计,并且NEON甚至具有vrsqrts
在单个指令中执行部分细化而无需加载常量的指令.
如果您可以访问 C++20 或更高版本,那么您可以使用std::bit_cast
float InverseSquareRoot(float x)
{
float xhalf = 0.5f*x;
int32_t i = std::bit_cast<int32_t>(x);
i = 0x5f3759df - (i>>1);
x = std::bit_cast<float>(i);
x = x*(1.5f - xhalf*x*x);
return x;
}
Run Code Online (Sandbox Code Playgroud)
目前,所有主要编译器都std::bit_cast
支持。请参阅Godbolt 上的演示
如果您使用的是旧版本的 Clang,您可以尝试__builtin_bit_cast
。像这样改变演员阵容
int32_t i = __builtin_bit_cast(std::int32_t, x);
x = __builtin_bit_cast(float, i);
Run Code Online (Sandbox Code Playgroud)
更新资料
由于我从委员会得到的反馈,我不再相信这个答案是正确的。但我想把它留作参考。我有目的地希望委员会能够纠正这个答案(如果它选择这样做的话)。也就是说,没有什么基础的硬件可以使这个答案不正确,这仅仅是委员会的判断是正确的还是错误的。
我添加的答案不是反驳已接受的答案,而是对其进行扩充。我相信公认的答案既正确又有效(我刚刚对此表示赞同)。但是,我想演示另一种同样正确有效的技术:
float InverseSquareRoot(float x)
{
union
{
float as_float;
int32_t as_int;
};
float xhalf = 0.5f*x;
as_float = x;
as_int = 0x5f3759df - (as_int>>1);
as_float = as_float*(1.5f - xhalf*as_float*as_float);
return as_float;
}
Run Code Online (Sandbox Code Playgroud)
使用在-O3处进行优化的clang ++,我编译了plasmacel的代码,R。Martinho Fernandes代码以及此代码,并逐行比较了组装。这三个都是相同的。这是由于编译器选择像这样进行编译。对于编译器生成不同的,已损坏的代码,同样有效。