我对 reinterpret_cast
与 static_cast
的适用性有点混淆。根据我的阅读,一般规则是当类型可以在编译时解释时使用静态转换,因此使用 static
一词。这也是 C++ 编译器在内部用于隐式转换的转换。
reinterpret_cast
适用于两种场景:
将整数类型转换为指针类型,反之亦然
将一种指针类型转换为另一种。我得到的一般想法是这是不可移植的,应该避免。
我有点困惑的是我需要的一种用法,我从 C 调用 C++,并且 C 代码需要保留 C++ 对象,所以基本上它包含 void*
。在 void *
和 Class 类型之间转换应该使用什么类型转换?
我见过 static_cast
和 reinterpret_cast
的用法吗?虽然从我一直在阅读的内容来看,static
更好,因为演员可以在编译时发生?虽然它说使用 reinterpret_cast
从一种指针类型转换为另一种?
reinterpret_cast
不会在运行时发生。它们都是编译时语句。来自 en.cppreference.com/w/cpp/language/reinterpret_cast:“与 static_cast 不同,但与 const_cast 类似,reinterpret_cast 表达式不会编译为任何 CPU 指令。它纯粹是一个编译器指令,它指示编译器将表达式的位序列(对象表示)视为具有类型 new_type。”
C++ 标准保证以下内容:
static_cast
指向和来自 void*
的指针会保留地址。也就是说,在下面,a
、b
和 c
都指向同一个地址:
int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
reinterpret_cast
仅保证如果您将指针转换为不同的类型,然后reinterpret_cast
将其返回到原始类型,您将获得原始值。所以在下面:
int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
a
和 c
包含相同的值,但 b
的值未指定。 (实际上,它通常包含与 a
和 c
相同的地址,但标准中没有指定,并且在具有更复杂内存系统的机器上可能不是这样。)
对于与 void*
之间的转换,应首选 static_cast
。
需要 reinterpret_cast
的一种情况是与不透明数据类型交互时。这经常发生在程序员无法控制的供应商 API 中。这是一个人为的示例,其中供应商提供了一个用于存储和检索任意全局数据的 API:
// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();
要使用此 API,程序员必须将他们的数据转换为 VendorGlobalUserData
并再次返回。 static_cast
不起作用,必须使用 reinterpret_cast
:
// main.cpp
#include "vendor.hpp"
#include <iostream>
using namespace std;
struct MyUserData {
MyUserData() : m(42) {}
int m;
};
int main() {
MyUserData u;
// store global data
VendorGlobalUserData d1;
// d1 = &u; // compile error
// d1 = static_cast<VendorGlobalUserData>(&u); // compile error
d1 = reinterpret_cast<VendorGlobalUserData>(&u); // ok
VendorSetUserData(d1);
// do other stuff...
// retrieve global data
VendorGlobalUserData d2 = VendorGetUserData();
MyUserData * p = 0;
// p = d2; // compile error
// p = static_cast<MyUserData *>(d2); // compile error
p = reinterpret_cast<MyUserData *>(d2); // ok
if (p) { cout << p->m << endl; }
return 0;
}
下面是示例 API 的人为实现:
// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
void*
?
USpoofChecker*
的指针,其中 USpoofChecker
是一个空结构。但是,在后台,每当您传递 USpoofChecker*
时,它都会将 reinterpret_cast
传递给内部 C++ 类型。
struct_a*->void*->struct_a*
定义明确的原因。另一方面,struct_a*->void*->struct_b*
和直接 atruct_a->struct_b*
不是。
简短回答:如果您不知道 reinterpret_cast
代表什么,请不要使用它。如果你将来需要它,你会知道的。
完整答案:
让我们考虑基本的数字类型。
例如,当您将 int(12)
转换为 unsigned float (12.0f)
时,您的处理器需要调用一些计算,因为这两个数字具有不同的位表示。这就是 static_cast
的含义。
另一方面,当您调用 reinterpret_cast
时,CPU 不会调用任何计算。它只是将内存中的一组位视为具有另一种类型。因此,当您使用此关键字将 int*
转换为 float*
时,新值(在指针取消引用之后)与数学意义上的旧值无关(忽略读取此值是未定义行为的事实)。
请注意,在 reinterprt_cast'ing 之后读取或修改值通常是未定义行为。在大多数情况下,如果要实现某些数据的位表示,应该使用指向 std::byte
的指针或引用(从 C++17 开始),这几乎总是合法的操作。其他“安全”类型是 char
和 unsigned char
,但我会说它不应该在现代 C++ 中用于此目的,因为 std::byte
具有更好的语义。
示例: 确实,reinterpret_cast
不可移植,原因之一是字节顺序(字节序)。但这通常是使用它的最佳理由。让我们想象一下这个例子:你必须从文件中读取二进制 32 位数字,并且你知道它是大端。您的代码必须是通用的,并且可以在大端(例如某些 ARM)和小端(例如 x86)系统上正常工作。所以你必须检查字节顺序。 它在编译时是众所周知的,所以你可以编写 你可以编写一个函数来实现这一点:
constexpr
函数:
/*constexpr*/ bool is_little_endian() {
std::uint16_t x=0x0001;
auto p = reinterpret_cast<std::uint8_t*>(&x);
return *p != 0;
}
解释: x
在内存中的二进制表示可以是 0000'0000'0000'0001
(大)或 0000'0001'0000'0000
(小端)。在重新解释转换后,p
指针下的字节可能分别是 0000'0000
或 0000'0001
。如果您使用静态转换,无论使用何种字节序,它都将始终为 0000'0001
。
编辑:
在第一个版本中,我将示例函数 is_little_endian
设为 constexpr
。它在最新的 gcc (8.3.0) 上编译得很好,但标准说它是非法的。 clang 编译器拒绝编译它(这是正确的)。
short
在内存中占用 16 位。已更正。
reinterpret_cast
的含义未由 C++ 标准定义。因此,理论上 reinterpret_cast
可能会使您的程序崩溃。在实践中,编译器会尝试做你期望的事情,即解释你传入的内容,就好像它们是你要转换的类型一样。如果您知道要使用的编译器对 reinterpret_cast
做什么,您可以使用它,但说它是可移植 是在撒谎。
对于您描述的情况,以及几乎任何您可能考虑使用 reinterpret_cast
的情况,您可以使用 static_cast
或其他替代方法。除其他事项外,该标准还说明了您对 static_cast
的期望(第 5.2.9 节):
“指向 cv void 的指针”类型的右值可以显式转换为指向对象类型的指针。指向对象的指针类型的值转换为“指向 cv void 的指针”并返回到原始指针类型将具有其原始值。
因此,对于您的用例,标准化委员会打算让您使用 static_cast
似乎相当清楚。
reinterpret_crash
。编译器错误不会阻止我使我的重新解释程序崩溃。我会尽快报告错误!</irony>
template<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }
reinterpret_cast 的一种用途是,如果您想对 (IEEE 754) 浮点数应用按位运算。一个例子是快速平方根逆技巧:
https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code
它将浮点数的二进制表示视为整数,将其右移并从常数中减去,从而将指数减半并取反。转换回浮点数后,对其进行 Newton-Raphson 迭代以使该近似值更精确:
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 deuce?
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;
}
这最初是用 C 编写的,因此使用 C 转换,但类似的 C++ 转换是 reinterpret_cast。
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))
- ideone.com/6S4ijc
memcpy
替换所有 reinterpret_cast
,它仍然是 UB 吗?
memcpy
肯定会使其合法化。
这是 Avi Ginsburg 程序的一个变体,它清楚地说明了 Chris Luengo、flodin 和 cmdLP 提到的 reinterpret_cast
的属性:编译器将指向的内存位置视为新类型的对象:
#include <iostream>
#include <string>
#include <iomanip>
using namespace std;
class A
{
public:
int i;
};
class B : public A
{
public:
virtual void f() {}
};
int main()
{
string s;
B b;
b.i = 0;
A* as = static_cast<A*>(&b);
A* ar = reinterpret_cast<A*>(&b);
B* c = reinterpret_cast<B*>(ar);
cout << "as->i = " << hex << setfill('0') << as->i << "\n";
cout << "ar->i = " << ar->i << "\n";
cout << "b.i = " << b.i << "\n";
cout << "c->i = " << c->i << "\n";
cout << "\n";
cout << "&(as->i) = " << &(as->i) << "\n";
cout << "&(ar->i) = " << &(ar->i) << "\n";
cout << "&(b.i) = " << &(b.i) << "\n";
cout << "&(c->i) = " << &(c->i) << "\n";
cout << "\n";
cout << "&b = " << &b << "\n";
cout << "as = " << as << "\n";
cout << "ar = " << ar << "\n";
cout << "c = " << c << "\n";
cout << "Press ENTER to exit.\n";
getline(cin,s);
}
这导致输出如下:
as->i = 0
ar->i = 50ee64
b.i = 0
c->i = 0
&(as->i) = 00EFF978
&(ar->i) = 00EFF974
&(b.i) = 00EFF978
&(c->i) = 00EFF978
&b = 00EFF974
as = 00EFF978
ar = 00EFF974
c = 00EFF974
Press ENTER to exit.
可以看出,B 对象首先作为 B 特定数据构建在内存中,其次是嵌入的 A 对象。 static_cast
正确返回了嵌入的 A 对象的地址,并且由 static_cast
创建的指针正确地给出了数据字段的值。 reinterpret_cast
生成的指针将 b
的内存位置视为普通 A 对象,因此当指针尝试获取数据字段时,它返回一些 B 特定数据,就好像它是 this 的内容一样场地。
reinterpret_cast
的一种用途是将指针转换为无符号整数(当指针和无符号整数大小相同时):
int i;
unsigned int u = reinterpret_cast<unsigned int>(&i);
template <class outType, class inType>
outType safe_cast(inType pointer)
{
void* temp = static_cast<void*>(pointer);
return static_cast<outType>(temp);
}
我试图总结并使用模板编写了一个简单的安全演员表。请注意,此解决方案不保证在函数上转换指针。
reinterpret_cast
在这种情况下所做的:“对象指针可以显式转换为不同类型的对象指针。[72] 当对象指针类型的 prvalue v
转换为对象指针类型“pointer to cv T
”,结果为static_cast<cv T*>(static_cast<cv void*>(v))
。 ——N3797。
c++2003
标准,我可以 NOT 发现 reinterpret_cast
确实 static_cast<cv T*>(static_cast<cv void*>(v))
C++03
之前是 C++98
。大量项目使用旧的 C++ 而不是可移植的 C。有时您必须关心可移植性。例如,您必须在 Solaris、AIX、HPUX、Windows 上支持相同的代码。在编译器依赖性和可移植性方面,它是棘手的。因此,引入可移植性地狱的一个很好的例子是在您的代码中使用 reinterpret_cast
首先,您在此处有一些特定类型的数据,例如 int:
int x = 0x7fffffff://==nan in binary representation
然后您想访问与其他类型(如 float)相同的变量:您可以在
float y = reinterpret_cast<float&>(x);
//this could only be used in cpp, looks like a function with template-parameters
或者
float y = *(float*)&(x);
//this could be used in c and cpp
简而言之:这意味着相同的内存被用作不同的类型。因此,您可以将浮点数的二进制表示形式转换为像上面那样的 int 类型的浮点数。例如,0x80000000 为 -0(尾数和指数为空,但符号 msb 为一。这也适用于双精度数和长双精度数。
优化:我认为 reinterpret_cast 会在许多编译器中进行优化,而 c-casting 是通过指针算法进行的(必须将值复制到内存中,因为指针不能指向 cpu- 寄存器)。
注意:在这两种情况下,您都应该在转换前将转换的值保存在变量中!这个宏可以帮助:
#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
reinterpret_cast
中,从 int
到 float&
是未定义的行为。
memcpy
以尽可能纯粹地注册操作;演员表很容易(但也是 UB - 如果值是 used - 正如本页所指出的那样)。
快速回答:如果可以编译,则使用 static_cast
,否则使用 reinterpret_cast
。
阅读FAQ!在 C 中保存 C++ 数据可能是有风险的。
在 C++ 中,指向对象的指针可以转换为 void *
而无需任何强制转换。但反过来就不是这样了。您需要一个 static_cast
来取回原始指针。
reinterpret_cast
时,在 C++11 中不再未指定b
的值。并且在 C++03 中,禁止使用reinterpret_cast
将int*
转换为void*
(尽管编译器没有实现它并且它不切实际,因此在 C++11 中进行了更改)。