何时使用reinterpret_cast?

时间:2009-02-21 16:12:40

标签: c++ casting

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

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

我有点困惑的是我需要的一种用法,我从C调用C ++并且C代码需要保持C ++对象,所以基本上它拥有void*。应使用什么演员表来转换void *和类型?

我看过static_castreinterpret_cast的使用情况?虽然从我读过的内容看来static更好,因为演员阵容可以在编译时发生?虽然它说使用reinterpret_cast从一种指针类型转换为另一种指针类型?

11 个答案:

答案 0 :(得分:386)

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相同的地址,但是标准中没有指定,并且在具有更复杂内存系统的机器上可能不是这样。)

对于往返于*的投射,static_cast应该是首选。

答案 1 :(得分:138)

需要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; }

答案 2 :(得分:76)

答案简短: 如果您不知道reinterpret_cast代表什么,请不要使用它。如果您将来需要它,您就会知道。

完整答案:

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

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

另一方面,当您调用 reinterpret_cast 时,CPU不会调用任何计算。它只是处理内存中的一组位,就像它有另一种类型一样。因此,当您使用此关键字将int*转换为float*时,新值(在指针取消引用后)与数学含义中的旧值无关。

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

修改

在第一个版本中,我将示例函数is_little_endian设为constexpr。它在最新的gcc(8.3.0)上编译好,但标准认为它是非法的。 clang编译器拒绝编译它(这是正确的)。

答案 3 :(得分:18)

reinterpret_cast的含义不是由C ++标准定义的。因此,理论上reinterpret_cast可能会导致程序崩溃。在实践中,编译器会尝试按照您的期望进行操作,即解释您传入的内容,就好像它们是您要投射的类型一样。如果您知道将要使用的编译器与reinterpret_cast一起使用,您可以使用它,但要说它是便携式就会撒谎。

对于您描述的案例,以及您可能会考虑使用reinterpret_cast的任何情况,您可以使用static_cast或其他替代方案。除此之外,标准还有关于static_cast(§5.2.9)所期望的内容:

  

“指向cv void的指针”类型的右值可以显式转换为指向对象类型的指针。转换为“指向cv void的指针”并返回原始指针类型的对象的类型指针值将具有其原始值。

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

答案 4 :(得分:11)

reinterpret_cast的一个用途是,如果要将按位运算应用于(IEEE 754)浮点数。其中一个例子是Fast Inverse Square-Root技巧:

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

它将float的二进制表示形式视为整数,将其向右移动并从常量中减去它,从而将指数减半和否定。转换回浮点数后,它会进行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。

答案 5 :(得分:3)

您可以使用reinterprete_cast在编译时检查继承 看这里: Using reinterpret_cast to check inheritance at compile time

答案 6 :(得分:1)

template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

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

答案 7 :(得分:1)

使用reinterpret_cast的一个原因是基类没有vtable,而派生类却有。在这种情况下,static_castreinterpret_cast将导致指针值不同(这是jalf above所提到的非典型情况)。就像免责声明一样,我并不是在说这是标准的一部分,而是几个广泛编译器的实现。

以下面的代码为例:

#include <cstdio>

class A {
public:
    int i;
};

class B : public A {
public:
    virtual void func() {  }
};

int main()
{
    B b;
    const A* a = static_cast<A*>(&b);
    const A* ar = reinterpret_cast<A*>(&b);

    printf("&b = %p\n", &b);
    printf(" a = %p\n", a);
    printf("ar = %p\n", ar);
    printf("difference = %ld\n", (long int)(a - ar));

    return 0;
}

输出类似:

  

&b = 0x7ffe10e68b38
   a = 0x7ffe10e68b40
  ar = 0x7ffe10e68b38
  差异= 2

在我尝试过的所有编译器中(MSVC 2015&2017,clang 8.0.0,gcc 9.2,icc 19.0.1-参见godbolt for the last 3),static_cast的结果与{{1 }}乘2(MSVC为4)。唯一警告差异的编译器是clang,

  

17:16:警告:从类'B *'到其非零偏移量'A *'的基址的'reinterpret_cast'行为不同于'static_cast'[-Wreinterpret-base-class]
          const A * ar = reinterpret_cast(&b);
                        ^ ~~~~~~~~~~~~~~~~~~~~~~
  17:16:注意:向上投射时,请使用'static_cast'正确调整指针
          const A * ar = reinterpret_cast(&b);
                        ^ ~~~~~~~~~~~~~~~
                        static_cast

最后一个警告是,如果基类没有数据成员(例如reinterpret_cast),则clang,gcc和icc返回的int i;reinterpret_cast的地址相同,而MSVC仍然没有。

答案 8 :(得分:1)

这里是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的一种用法是将指针转换为无符号整数(当指针和无符号整数的大小相同时):

int i; unsigned int u = reinterpret_cast<unsigned int>(&i);

答案 9 :(得分:-5)

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

答案 10 :(得分:-13)

阅读FAQ!用C语言保存C ++数据可能存在风险。

在C ++中,指向对象的指针可以转换为void *而无需任何强制转换。但反之亦然。你需要一个static_cast来获取原始指针。