关于c ++:何时使用reinterpret_cast?

When to use reinterpret_cast?

我对reinterpret_caststatic_cast的适用性有点困惑。根据我所读到的,一般规则是在编译时可以解释类型时使用静态强制转换,因此出现了单词static。这是C++编译器内部使用的用于隐式转换的强制转换。

reinterpret_casts适用于两种情况,将整数类型转换为指针类型,反之亦然,或者将一种指针类型转换为另一种。我得到的一般想法是这是不可移植的,应该避免。

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

我见过static_castreinterpret_cast的用法吗?虽然从我读到的内容来看,static似乎更好,因为强制转换可以在编译时发生?虽然它说使用reinterpret_cast从一个指针类型转换到另一个指针类型?


C++标准保证如下:

static_cast使用指向void*的指针保存地址。也就是说,在下面的a、b和c都指向同一个地址:

1
2
3
int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_cast只保证如果您将一个指针强制转换到另一个类型,然后reinterpret_cast返回到原来的类型,就可以得到原来的值。因此,在以下内容中:

1
2
3
int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

A和C包含相同的值,但B的值未指定。(实际上,它通常包含与A和C相同的地址,但在标准中没有指定,在具有更复杂内存系统的计算机上可能不正确。)

对于空隙*之间的铸件,应优先选用static_cast


当需要reinterpret_cast时,一种情况是与不透明数据类型进行接口。这经常发生在程序员无法控制的供应商API中。下面是一个人为设计的示例,其中供应商提供用于存储和检索任意全局数据的API:

1
2
3
4
// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();

要使用这个API,程序员必须将他们的数据强制转换到VendorGlobalUserData,然后再返回。static_cast不起作用,必须使用reinterpret_cast

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 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的人为实现:

1
2
3
4
// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }


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

完全回答:

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

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

另一方面,当您调用reinterpret_cast时,CPU不调用任何计算。它只处理内存中的一组位,就像处理另一个类型一样。所以当你用这个关键字把int*转换成float*时,新的值(指针删除后)与数学意义上的旧值无关。

示例:由于一个原因,reinterpret_cast是不可移植的—字节顺序(endianness)。但这往往是令人惊讶的最佳使用理由。让我们想象一下这个例子:您必须从文件中读取二进制32位数字,并且您知道它是big endian。您的代码必须是通用的,并且可以在big-endian(如某些ARM)和little-endian(如x86)系统上正常工作。所以你必须检查字节顺序。it is known on compile time so you can write constexprfunction:You can write a function to achieve this:

1
2
3
4
5
/*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_endian设为constexpr。它在最新的GCC(8.3.0)上编译罚款,但该标准称这是非法的。clang编译器拒绝编译它(正确)。


EDCOX1 2含义的含义不是由C++标准定义的。因此,理论上,reinterpret_cast可能会使您的程序崩溃。在实践中,编译器尝试执行您期望的操作,即解释您传递的内容的位,就好像它们是您要强制转换的类型一样。如果你知道你将要使用的编译器对reinterpret_cast做什么,你就可以使用它,但是说它是可移植的,那就大错特错了。

对于您所描述的情况,以及几乎任何您可能认为是reinterpret_cast的情况,您可以使用static_cast或其他替代方法。除其他事项外,本标准还规定了您对static_cast的期望(§5.2.9):

An rvalue of type"pointer to cv void" can be explicitly converted to a pointer to object type. A value of type pointer to object converted to"pointer to cv void" and back to the original pointer type will have its original value.

因此,对于您的用例,标准化委员会似乎非常清楚地打算让您使用static_cast


reinterpret-cast的一个用法是,如果您想对(ieee 754)浮点数应用位操作。其中一个例子是快速平方根反比技巧:

https://en.wikipedia.org/wiki/fast_-inverse_-square_-root概述_-the-code

它将浮点的二进制表示视为一个整数,将其右移并从常量中减去它,从而将指数减半并求反。在转换回浮点数后,它将进行牛顿-拉斐逊迭代以使此近似更精确:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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++ CAST是重新解释。


您可以在编译时使用reinterprete-cast检查继承。看这里:在编译时使用reinterpret-cast检查继承


首先,您有一些特定类型的数据,如int:

1
int x = 0x7fffffff://==nan in binary representation

然后要访问与其他类型(如float)相同的变量:你可以决定

1
2
3
float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

1
2
3
float y = *(float*)&(x);

//this could be used in c and cpp

简而言之:这意味着相同的内存被用作不同的类型。所以您可以将浮点数的二进制表示形式转换为int类型,如上面所述。例如,0x8000000为-0(尾数和指数为空,但符号msb为一。这也适用于双打和长双打。

优化:我认为在许多编译器中重新解释RETU CAST会得到优化,而C-CAST是由pointerarithmetic生成的(值必须复制到内存中,因为指针不能指向CPU寄存器)。

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

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

1
2
3
4
5
6
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

我试图得出结论,并使用模板编写了一个简单的安全强制转换。请注意,此解决方案不保证在函数上强制转换指针。


快速回答:如果编译,使用static_cast,否则使用reinterpret_cast


阅读常见问题!在C中保存C++数据可能有风险。

在C++中,指向对象的指针可以被转换为EDCOX1×0,而不需要任何类型的转换。但反过来说,这不是真的。你需要一个static_cast来恢复原始指针。