jan*_*b04 35 c++ performance casting language-lawyer type-punning
似乎有两种类型的 C++。实用C++和语言律师C++。在某些情况下,能够将一种类型的位模式解释为一种不同的类型会很有用。浮点技巧就是一个显着的例子。让我们取著名的快速平方根反比(取自Wikipedia,又取自此处):
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
// y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
return y;
}
Run Code Online (Sandbox Code Playgroud)
撇开细节不谈,它使用 IEEE-754 浮点位表示的某些属性。这里有趣的部分是*(long*)从float*to的演员表long*。C 和 C++ 之间在哪些类型的这种重新解释强制转换是定义的行为方面存在差异,但实际上这种技术在两种语言中都经常使用。
问题是,对于这样一个简单的问题,上面介绍的方法和其他不同的方法可能会出现很多陷阱。列举一些:
同时,执行类型双关的方法有很多,相关的机制也很多。我能找到的只有这些:
reinterpret_cast 和 c 风格的演员
[[nodiscard]] float int_to_float1(int x) noexcept
{
return *reinterpret_cast<float*>(&x);
}
[[nodiscard]] float int_to_float2(int x) noexcept
{
return *(float*)(&x);
}
Run Code Online (Sandbox Code Playgroud)
static_cast 和 void*
[[nodiscard]] float int_to_float3(int x) noexcept
{
return *static_cast<float*>(static_cast<void*>(&x));
}
Run Code Online (Sandbox Code Playgroud)
std::bit_cast
[[nodiscard]] constexpr float int_to_float4(int x) noexcept
{
return std::bit_cast<float>(x);
}
Run Code Online (Sandbox Code Playgroud)
memcpy
[[nodiscard]] float int_to_float5(int x) noexcept
{
float destination;
memcpy(&destination, &x, sizeof(x));
return destination;
}
Run Code Online (Sandbox Code Playgroud)
union
[[nodiscard]] float int_to_float6(int x) noexcept
{
union {
int as_int;
float as_float;
} destination{x};
return destination.as_float;
}
Run Code Online (Sandbox Code Playgroud)
安置new和std::launder
[[nodiscard]] float int_to_float7(int x) noexcept
{
new(&x) float;
return *std::launder(reinterpret_cast<float*>(&x));
}
Run Code Online (Sandbox Code Playgroud)
std::byte
[[nodiscard]] float int_to_float8(int x) noexcept
{
return *reinterpret_cast<float*>(reinterpret_cast<std::byte*>(&x));
}
Run Code Online (Sandbox Code Playgroud)
问题是这些方式中哪些是安全的,哪些是不安全的,哪些是永远被诅咒的。应该使用哪一个,为什么?是否有 C++ 社区接受的规范?为什么++的C的新版本引入更加机制std::launder在C ++ 17或者std::byte,std::bit_cast在C ++ 20?
给出一个具体问题:重写快速平方根反函数的最安全、最高效和最好的方法是什么?(是的,我知道维基百科上有一种方法的建议)。
编辑:为了增加混乱,似乎有一个建议建议添加另一种类型的双关机制:std::start_lifetime_as,这也在另一个问题中讨论过。
(天马行空)
Jér*_*ard 15
首先,你假设sizeof(long) == sizeof(int) == sizeof(float). 这并不总是正确的,并且完全未指定(取决于平台)。实际上,这在我使用 clang-cl 的 Windows 上是正确的,在使用相同 64 位机器的 Linux 上是错误的。同一操作系统/机器上的不同编译器会给出不同的结果。至少需要静态断言以避免偷偷摸摸的错误。
由于严格的别名规则,简单的 C 类型转换、重新解释类型转换和静态类型转换在这里是无效的(迂腐,在这种情况下,程序在 C++ 标准方面格式错误)。联合解决方案也无效(它仅在 C 中有效,在 C++ 中无效)。只有std::bit_cast和std::memcpy解决方案是“安全的”(假设类型的大小在目标平台上匹配)。使用std::memcpy通常很快,因为它已被大多数主流编译器优化(启用优化时,例如-O3GCC/Clang):std::memcpy调用可以被内联并由更快的指令替换。std::bit_cast是这样做的新方法(仅自 C++20 起)。最后一个解决方案对于 C++ 代码来说是更清晰的,因为它std::memcpy使用了不安全的void*类型,从而绕过了类型系统。
for*_*818 10
这是我从 gcc 11.1 得到的-O3:
int_to_float4(int):
movd xmm0, edi
ret
int_to_float1(int):
movd xmm0, edi
ret
int_to_float2(int):
movd xmm0, edi
ret
int_to_float3(int):
movd xmm0, edi
ret
int_to_float5(int):
movd xmm0, edi
ret
int_to_float6(int):
movd xmm0, edi
ret
int_to_float7(int):
mov DWORD PTR [rsp-4], edi
movss xmm0, DWORD PTR [rsp-4]
ret
int_to_float8(int):
movd xmm0, edi
ret
Run Code Online (Sandbox Code Playgroud)
我不得不添加 aauto x = &int_to_float4;来强制 gcc 为 实际发出任何东西int_to_float4,我想这就是它首先出现的原因。
我不太熟悉,std::launder所以我不知道为什么它不同。否则它们是相同的。这就是 gcc 必须说的(在这种情况下,带有那个标志)。标准所说的是不同的故事。虽然,memcpy(&destination, &x, sizeof(x));定义良好,大多数编译器都知道如何优化它。std::bit_cast是在 C++20 中引入的,以使这种强制转换更加明确。请注意,在 cppreference 的可能实现中,它们使用std::memcpy;)。
TL; 博士
重写快速平方根反函数的最安全、最高效和最好的方法是什么?
std::memcpy在 C++20 及更高版本中std::bit_cast。
| 归档时间: |
|
| 查看次数: |
1315 次 |
| 最近记录: |