在C ++中进行类型修饰的现代正确方法是什么? [英] What is the modern, correct way to do type punning in C++?
问题描述
似乎有两种类型的C ++.实用的C ++和语言律师C ++.在某些情况下,将一种类型的位模式解释为另一种类型可能很有用.浮点技巧是一个明显的例子.让我们以著名的快速逆平方根为基础(取自 Wikipedia ,该取自 Wikipedia ).href ="https://github.com/id-Software/Quake-III-Arena/blob/master/code/game/q_math.c#L552" rel ="noreferrer">此处):>
It seems like there are two types of C++. The practical C++ and the language lawyer C++. In certain situations it can be useful to be able to interpret a bit pattern of one type as if it were a different type. Floating point tricks are a notable example. Let's take the famous fast inverse square root (taken from Wikipedia, which was in turn taken from here):
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;
}
抛开细节,它使用IEEE-754浮点位表示的某些属性.有趣的部分是从 float *
转换为 long *
的 *(long *)
.C和C ++之间在定义行为的重定义类型方面存在差异,但是实际上在两种语言中都经常使用这种技术.
Setting aside details, it uses certain properties of the IEEE-754 floating point bit representation. The interesting part here is the *(long*)
cast from float*
to long*
. There are differences between C and C++ about which types of such reinterpreting casts are defined behaviour, however in practice such techniques are used often in both languages.
问题是,对于这样一个简单的问题,上面介绍的方法和其他方法可能会遇到很多陷阱.列举一些:
The thing is that for such a simple problem there are a lot of pitfalls that can occur with the approach presented above and different others. To name some:
同时,有许多执行类型修剪的方法以及与之相关的许多机制.这些都是我能找到的:
At the same time, there are a lot of ways of performing type punning and a lot of mechanisms related to it. These are all that I could find:
-
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);
}
static_cast
和 void *
[[nodiscard]] float int_to_float3(int x) noexcept
{
return *static_cast<float*>(static_cast<void*>(&x));
}
std :: bit_cast
[[nodiscard]] constexpr float int_to_float4(int x) noexcept
{
return std::bit_cast<float>(x);
}
memcpy
[[nodiscard]] float int_to_float5(int x) noexcept
{
float destination;
memcpy(&destination, &x, sizeof(x));
return destination;
}
联盟
[[nodiscard]] float int_to_float6(int x) noexcept
{
union {
int as_int;
float as_float;
} destination{x};
return destination.as_float;
}
放置 new
和 std :: launder
[[nodiscard]] float int_to_float7(int x) noexcept
{
new(&x) float;
return *std::launder(reinterpret_cast<float*>(&x));
}
std :: byte
[[nodiscard]] float int_to_float8(int x) noexcept
{
return *reinterpret_cast<float*>(reinterpret_cast<std::byte*>(&x));
}
问题是这些方法中哪些是安全的,哪些是不安全的,哪些将永远被诅咒.应该使用哪一个?为什么?C ++社区是否接受规范的规范?为什么新版本的C ++会在C ++ 17中引入更多机制 std :: launder
或 std :: byte
, std :: bit_cast
在C ++ 20中?
The question is which of these ways are safe, which are unsafe and which are damned forever. Which one should be used and why? Is there a canonical one accepted by the C++ community? Why are new versions of C++ introducing even more mechanisms std::launder
in C++17 or std::byte
, std::bit_cast
in C++20?
要提出一个具体的问题:什么是重写快速反平方根函数的最安全,最有效和最好的方法?(是的,我知道有人建议使用一种方法维基百科).
To give a concrete problem: what would be the safest, most performant and best way to rewrite the fast inverse square root function? (Yes, I know that there is a suggestion of one way on Wikipedia).
( godbolt )
推荐答案
首先,您假设 sizeof(long)== sizeof(int)== sizeof(float)
.这并不总是正确的,并且完全没有指定(取决于平台).实际上,这在使用clang-cl的Windows上是正确的,而在使用同一64位计算机的Linux上则是错误的.同一台OS/计算机上的不同编译器可以给出不同的结果.至少需要一个静态断言才能避免偷偷摸摸的错误.
First of all, you assume that sizeof(long) == sizeof(int) == sizeof(float)
. This is not always true, and totally unspecified (platform dependent). Actually, this is true on my Windows using clang-cl and wrong on my Linux using the same 64-bit machine. Different compilers on the same OS/machine can give different results. A static assert is at least required to avoid sneaky bugs.
纯C强制转换,重新解释强制转换和静态强制转换在这里是无效的,因为严格的别名规则(为方便起见,在这种情况下,有关C ++标准的程序格式不正确).联合解决方案也无效(仅在C中有效,在C ++中无效).只有 std :: bit_cast
和 std :: memcpy
解决方案是安全"的,(假设类型的大小在目标平台上匹配).使用 std :: memcpy
通常是快速的,因为大多数主流编译器已经对其进行了优化(启用优化功能后,例如对GCC/Clang使用 -O3
进行优化):std :: memcpy
调用可以被内联并替换为更快的指令. std :: bit_cast
是执行此操作的新方法(仅自C ++ 20起).最后一种解决方案是C ++代码更干净,因为 std :: memcpy
使用不安全的 void *
类型,从而绕过类型系统.
Plain C casts, reinterpret casts and static casts are invalid here because of the strict aliasing rule (to be pedantic, the program is ill-formed in this case regarding the C++ standard). The union solution is not valid too (it is only valid in C, not in C++). Only the std::bit_cast
and the std::memcpy
solution are "safe" (assuming the size of the types are matching on the target plateform). Using std::memcpy
is often fast as it is optimized by most mainstream compiler (when optimizations are enabled, like with -O3
for GCC/Clang): the std::memcpy
call can be inlined and replaced by faster instructions. std::bit_cast
is the new way of doing this (only since C++20). The last solution is cleaner for a C++ code as std::memcpy
use unsafe void*
types and thus by-pass the type system.
这篇关于在C ++中进行类型修饰的现代正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!