reinterpret_cast有两种情况适用:
- 将整数类型转换为指针类型或反之亦然
- 将一个指针类型转换为另一个。我的一般理解是,这是不可移植的,应该避免使用。
我见过使用static_cast和reinterpret_cast的例子。尽管我所阅读的内容显示static_cast更好,因为转换可以在编译时发生,但它也说要使用reinterpret_cast将一个指针类型转换为另一个。
C++标准保证以下内容:
将指针从void*
进行static_cast
转换后,地址会被保留。也就是说,在下面的代码中,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*
进行强制转换和强制转换到void*
,应该优先使用static_cast
。
reinterpret_cast
时,C++11中的b
值不再是未指定的。而在C++03中,使用reinterpret_cast
将int*
转换为void*
是被禁止的(尽管编译器没有实现这一点并且也很不实用,因此在C++11中进行了更改)。 - Johannes Schaub - litbreinterpret_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*
呢? - XeoUSpoofChecker*
类型的指针,其中 USpoofChecker
是一个空结构体。然而,在后台,每当您传递一个 USpoofChecker*
时,它都会经历 reinterpret_cast
转换成内部的C++类型。 - sffcstruct_a*->void*->struct_a*
是被定义良好的原因。另一方面,struct_a*->void*->struct_b*
和直接的 atruct_a->struct_b*
则不是。 - Mariusz Jaskółka简短回答:
如果你不知道reinterpret_cast
是什么意思,就不要使用它。如果将来需要用到它,你会知道的。
详细回答:
让我们考虑基本数字类型。
例如,当你将int(12)
转换为float (12.0f)
时,处理器需要调用一些计算,因为这两个数字具有不同的位表示。这就是static_cast
的作用。
另一方面,当你调用reinterpret_cast
时,CPU不会调用任何计算。它只是像拥有另一种类型的内存中的一组位一样处理。因此,当你使用这个关键字将int*
转换为float*
时,新值(在指针解引用之后)与数学含义上的旧值无关(忽略读取该值是未定义行为的事实)。
请注意,在重新解释转换后读取或修改值往往是未定义行为。在大多数情况下,如果您想要实现某些数据的位表示,则应使用指向或引用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
(big-endian)或0000'0001'0000'0000
(little-endian)。在将指针p
下的字节重新解释转换后,它们分别可以是0000'0000
或0000'0001
。如果使用静态转换,则无论使用哪种字节序,它始终为0000'0001
。
编辑:
在第一个版本中,我让示例函数is_little_endian
成为constexpr
。它在最新的gcc(8.3.0)上编译正常,但标准说这是非法的。clang编译器拒绝编译它(这是正确的)。
short
在内存中占据了16位。已经更正。 - Mariusz Jaskółkareinterpret_cast
的含义在 C++ 标准中没有定义。因此,在理论上,reinterpret_cast
可能会导致程序崩溃。实际上,编译器会试图做你所期望的事情,即将你传递的位解释为你进行强制类型转换的类型。如果你知道你要使用的编译器对 reinterpret_cast
做了什么,你可以使用它,但说它是“可移植”的是不正确的。reinterpret_cast
的情况,你都可以使用 static_cast
或其他一些替代方法。标准中有这样的规定(§5.2.9),关于你可以期望 static_cast
的行为:static_cast
。reinterpret_crash
,但是它却拒绝进行编译。虽然编译器的 Bug 阻止了我崩溃重新解释程序,但是我不会放弃。我会立即报告这个 Bug! - paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }
这段代码中的模板函数reinterpret_crash
接受两个模板参数T
和U
,并以一个参数a
作为输入。函数返回空指针(T*)nullptr
所解释的类型的内容,但由于空指针无法访问任何内存地址,因此函数会导致程序崩溃。 - user142019reinterpret_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。
memcpy
替换所有的reinterpret_cast
,这样还会有未定义行为吗? - sandthornmemcpy
肯定会使其合法化。 - Cris Luengoreinterpret_cast
的特性,这个特性在 Chris Luengo、flodin 和 cmdLP 中提到:编译器将指向的内存位置视为新类型的对象。#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.
static_cast
正确返回嵌入的A对象的地址,由 static_cast
创建的指针正确地给出了数据字段的值。 由 reinterpret_cast
生成的指针将b的内存位置视为普通的A对象,因此当指针尝试获取数据字段时,它返回一些B特定的数据,就好像这是该字段的内容。
reinterpret_cast
的一个用途是将指针转换为无符号整数(当指针和无符号整数的大小相同时):
int i;
unsigned int u = reinterpret_cast<unsigned int>(&i);
首先,你有一些特定类型的数据,比如这里的int:
int x = 0x7fffffff://==nan in binary representation
如果您想要将同一变量作为其他类型(如浮点数)访问: 您可以选择以下方式:
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
#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
int
到float&
的reinterpret_cast
形式是未定义行为。 - Mariusz Jaskółkamemcpy
优化为纯寄存器操作;强制转换很容易(但也是UB——如果值被使用——正如本页面上所指出的那样)。 - Davis Herringtemplate <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
转换为对象指针类型“指向_cv_ T
”,结果是static_cast<cv T*>(static_cast<cv void*>(v))
。”-- N3797。 - underscore_dc++2003
标准,我找不到 reinterpret_cast
能够执行 static_cast<cv T*>(static_cast<cv void*>(v))
的内容。 - Саша ЗезюлинскийC++03
之前是 C++98
。许多项目使用旧的 C++ 而不是可移植的 C++。有时你必须关心可移植性。例如,你必须支持相同的代码在 Solaris、AIX、HPUX 和 Windows 上运行。当涉及到编译器依赖和可移植性时,这是棘手的。因此,在你的代码中使用 reinterpret_cast
是引入可移植性问题的一个很好的例子。 - Саша Зезюлинскийsafe_cast
函数与 reinterpret_cast
做的事情完全相同:https://dev59.com/GMHqa4cB1Zd3GeqPuzZY#68137312。 - anton_rhstatic_cast
,否则请使用reinterpret_cast
。
reinterpret_cast
没有发生在运行时,它们都是编译时语句。来自 http://en.cppreference.com/w/cpp/language/reinterpret_cast 的解释:与 static_cast 不同,但类似于 const_cast,reinterpret_cast 表达式不会编译为任何 CPU 指令。它纯粹是一个编译器指令,告诉编译器将表达式的位序列(对象表示)视为具有新类型。 - Cris Luengo