何时使用reinterpret_cast?

我对reinterpret_cast vs static_cast的适用性有点困惑。据我所知,一般规则是当类型可以在编译时解释时使用静态强制转换,因此有了static这个词。这也是c++编译器内部用于隐式类型转换的类型转换。

# eyz0适用于两种情况:

  • 将整数类型转换为指针类型,反之亦然
  • 将一种指针类型转换为另一种。我得到的一般想法是,这是不可移植的,应该避免。

我有点困惑的地方是我需要的一种用法,我从C调用c++, C代码需要保持c++对象,所以基本上它持有void*。应该使用什么类型转换在void *和类类型之间?

我已经看到使用static_castreinterpret_cast?虽然从我所读到的似乎static更好,因为强制转换可以在编译时发生?虽然它说使用reinterpret_cast从一种指针类型转换到另一种?

436561 次浏览

阅读常见问题解答!在C语言中保存c++数据是有风险的。

在c++中,指向对象的指针可以被转换为void *而不需要任何类型强制转换。但反过来就不是这样了。您需要一个static_cast来取回原始指针。

reinterpret_cast的含义不是由c++标准定义的。因此,理论上reinterpret_cast可能导致程序崩溃。在实践中,编译器试图做你所期望的事情,也就是解释你传入的二进制位,就好像它们是你要强制转换的类型一样。如果你知道你将要使用的编译器对reinterpret_cast做什么,你就可以使用它,但是说它是可移植的是在撒谎。

对于您描述的情况,以及您可能会考虑reinterpret_cast的大多数情况,您可以使用static_cast或其他替代方案。除此之外,该标准还说明了你对static_cast(§5.2.9)的期望:

类型为“指向cv void的指针”的右值可以显式转换为指向对象类型的指针。一个类型为pointer to object的值转换为" pointer to cv void "并返回到原始指针类型将有其原始值。

因此,对于您的用例,标准化委员会显然希望您使用static_cast

c++标准保证了以下内容:

指向和指向void*的指针会保留地址。也就是说,下面的abc都指向同一个地址:

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);

ac包含相同的值,但b的值未指定。(在实践中,它通常包含与ac相同的地址,但在标准中没有指定,而且在具有更复杂内存系统的机器上可能不是这样。)

对于从void*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; }
template <class outType, class inType>
outType safe_cast(inType pointer)
{
void* temp = static_cast<void*>(pointer);
return static_cast<outType>(temp);
}
我试图总结并使用模板编写了一个简单的安全强制转换。 注意,此解决方案不保证转换函数上的指针
你可以使用reinterprete_cast在编译时检查继承 看这里: # EYZ0 < / p >

reinterpret_cast的一个用途是如果你想对(IEEE 754)浮点应用位操作。其中一个例子是快速平方根逆技巧:

https://en.wikipedia.org/wiki/Fast_inverse_square_root#Overview_of_the_code

它将浮点数的二进制表示形式视为整数,将其右移并从常数中减去,从而将指数对半和负。在转换回浮点数后,它会进行牛顿-拉弗森迭代,以使这个近似更加精确:

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。

快速回答:如果编译使用static_cast,否则使用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

BRIEF:这意味着相同的内存被用作不同的类型。所以你可以像上面那样将浮点数的二进制表示形式转换为int类型。例如,0x80000000为-0(尾数和指数为空,但符号msb为1。这也适用于双打和长双打。

优化:我认为reinterpret_cast会在许多编译器中进行优化,而c-casting是由指针算术进行的(值必须复制到内存中,因为指针不能指向cpu-寄存器)。

注意:在这两种情况下,您都应该在强制转换之前将强制转换的值保存在变量中!这个宏可以帮助:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })

简短的回答是: 如果你不知道reinterpret_cast代表什么,就不要用它。如果你将来需要它,你会知道的

完整的回答:

让我们考虑基本的数字类型。

例如,当您将int(12)转换为unsigned float (12.0f)时,您的处理器需要调用一些计算,因为这两个数字具有不同的位表示。这就是static_cast所代表的。

另一方面,当你调用reinterpret_cast时,CPU不会调用任何计算。它只是把内存中的一组位当作另一种类型来处理。因此,当您使用该关键字将int*转换为float*时,新值(指针解除引用后)在数学意义上与旧值没有任何关系(忽略读取此值是未定义行为的事实)。

注意,在reinterprt_cast'ing之后读取或修改值通常是未定义的行为。在大多数情况下,如果你想要实现一些数据的位表示,你应该使用指针或引用std::byte(从c++ 17开始),这几乎总是一个合法的操作。其他“safe"类型是charunsigned char,但我认为在现代c++中不应该用于此目的,因为std::byte具有更好的语义。

reinterpret_cast确实是不可移植的,因为一个原因-字节顺序(字节顺序)。但令人惊讶的是,这往往是使用它的最佳理由。让我们想象一下这个例子:你必须从文件中读取二进制32位数字,并且你知道它是大端序的。你的代码必须是通用的,并且在大端序(例如ARM)和小端序(例如x86)系统上正常工作。所以你必须检查字节顺序。你可以写一个函数来实现这个:

/*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'00000000'0001。如果使用静态强制转换,则无论使用什么字节顺序,它都将始终是0000'0001

编辑:

在第一个版本中,我使示例函数is_little_endianconstexpr。它在最新的gcc(8.3.0)上编译良好,但标准说它是非法的。clang编译器拒绝编译它(这是正确的)

下面是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的数据,就像它是该字段的内容一样。

reinterpret_cast的一个用途是将指针转换为无符号整数(当指针和无符号整数大小相同时):

< p > # EYZ0 # EYZ0 < / p >