下面的代码通过一些攻击执行快速反平方根操作。 该算法可能是由Silicon Graphics在1990年代早期开发的,它也出现在Quake 3中。 more info
但是我从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;
}
答案 0 :(得分:34)
忘记演员表。使用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;
原始代码尝试通过首先通过int32_t
指针访问float
对象来初始化int32_t
,这是规则被破坏的地方。 C风格的演员阵容相当于reinterpret_cast
,因此将其更改为reinterpret_cast
不会产生太大影响。
使用memcpy时的重要区别是字节从float
复制到int32_t
,但float
对象永远不会通过int32_t
左值访问,因为memcpy
指向void并且其内部是“神奇的”并且不会破坏别名规则。
答案 1 :(得分:12)
这里有一些很好的答案可以解决打字问题。
我想解决“快速反平方根”部分。不要在现代处理器上使用这个“技巧”。每个主流矢量ISA都有一个专用的硬件指令,为您提供快速的反平方根。它们中的每一个都比这个经常复制的小黑客更快,更准确。
这些说明都可以通过内在函数获得,因此它们相对容易使用。在SSE中,您想要使用rsqrtss
(内在:_mm_rsqrt_ss( )
);在NEON中你想使用vrsqrte
(内在:vrsqrte_f32( )
);在AltiVec中,您想要使用frsqrte
。大多数GPU ISA具有类似的指令。这些估计可以使用相同的牛顿迭代进行细化,NEON甚至可以使用vrsqrts
指令在单个指令中完成部分细化,而无需加载常量。
答案 2 :(得分:4)
<强>更新强>
由于我从委员会得到的反馈,我不再相信这个答案是正确的 t。但我想把它留作信息用途。我有目的地希望委员会能够纠正这个答案(如果它选择的话)。即没有任何关于底层硬件使得这个答案不正确,只是委员会的判断是否如此,或者不是这样。
我正在添加一个答案,不是要反驳接受的答案,而是要加以补充。我相信接受的答案既正确又有效(我刚刚赞成它)。但是,我想展示另一种同样正确和有效的技术:
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;
}
在-O3使用clang ++和优化,我编译了plasmacel的代码,R. Martinho Fernandes代码和这段代码,并逐行比较了装配。这三个都是相同的。 这是由于编译器选择像这样编译它。编译器生成不同的破解代码同样有效。
答案 3 :(得分:1)
演员表调用未定义的行为。无论你使用什么形式的演员,它仍然是未定义的行为。无论你使用什么类型的演员表,它都是未定义的。
大多数编译器都会按照你的期望做,但gcc喜欢它的意思,并且可能会假设你没有指定指针,尽管你做了所有的指示并重新排序操作,所以它们给出了一些奇怪的结果。
将指针转换为不兼容的类型并取消引用它是一种未定义的行为。唯一的例外是将它转换为char或来自char,因此唯一的解决方法是使用std::memcpy
(根据R. Martinho Fernandes的回答)。 (我不确定使用工会定义了多少;它确实有更好的工作机会)。
那就是说,你不应该在C ++中使用C风格的强制转换。在这种情况下,static_cast
无法编译,dynamic_cast
也不会强制您使用reinterpret_cast
和reinterpret_cast
强烈建议您可能违反严格的别名规则。
答案 4 :(得分:1)
这里唯一可以使用的演员是reinterpret_cast
。 (和
即便如此,至少有一个编译器会不断前进
确保它不起作用。)
但你究竟想做什么?当然可以
一个更好的解决方案,不涉及类型惩罚。有
极少数情况下,类型惩罚是合适的,他们
所有都是非常非常低级的代码,比如序列化,
或实现C标准库(例如
modf
)。否则(甚至可能在序列化中),功能
像ldexp
和modf
可能会更好,当然也可以
更具可读性
答案 5 :(得分:1)
请查看this以获取有关类型惩罚和严格别名的更多信息。
类型中唯一安全的数组转换为char
数组。如果您希望将一个数据地址切换为不同类型,则需要使用union
答案 6 :(得分:1)
如果您有权使用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;
}
目前std::bit_cast
是only supported by MSVC。参见demo on Godbolt
在等待实现时,如果您使用的是Clang,则可以尝试__builtin_bit_cast
。像这样改变演员表
int32_t i = __builtin_bit_cast(std::int32_t, x);
x = __builtin_bit_cast(float, i);
答案 7 :(得分:0)
基于此处的答案,我制作了现代的“伪广播”功能以简化应用程序。
C99版本 (虽然大多数编译器都支持它,但从理论上讲,某些行为可能是未定义的行为)
this.storage.store
通用版本 (基于接受的答案)
尺寸相同的广播类型:
template <typename T, typename U>
inline T pseudo_cast(const U &x)
{
static_assert(std::is_trivially_copyable<T>::value && std::is_trivially_copyable<U>::value, "pseudo_cast can't handle types which are not trivially copyable");
union { U from; T to; } __x = {x};
return __x.to;
}
任意大小的广播类型:
#include <cstring>
template <typename T, typename U>
inline T pseudo_cast(const U &x)
{
static_assert(std::is_trivially_copyable<T>::value && std::is_trivially_copyable<U>::value, "pseudo_cast can't handle types which are not trivially copyable");
static_assert(sizeof(T) == sizeof(U), "pseudo_cast can't handle types with different size");
T to;
std::memcpy(&to, &x, sizeof(T));
return to;
}
使用方式如下:
#include <cstring>
template <typename T, typename U>
inline T pseudo_cast(const U &x)
{
static_assert(std::is_trivially_copyable<T>::value && std::is_trivially_copyable<U>::value, "pseudo_cast can't handle types which are not trivially copyable");
T to = T(0);
std::memcpy(&to, &x, (sizeof(T) < sizeof(U)) ? sizeof(T) : sizeof(U));
return to;
}