何时使用reinterpret_cast?

Her*_*arn 432 c++ casting

我对reinterpret_castvs 的适用性感到困惑static_cast.从我读到的一般规则是使用静态转换,当类型可以在编译时解释,因此这个词static.这是C++编译器内部用于隐式转换的转换.

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

我有点困惑的地方是我需要的一种用法,我从C调用C++并且C代码需要保持C++对象,所以基本上它拥有一个void*.什么演员应该用于在void *类型和类型之间进行转换?

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

jal*_*alf 417

C++标准保证以下内容:

static_cast指向和void*保留地址的指针.也就是说,在下面,a,b和c都指向相同的地址:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);
Run Code Online (Sandbox Code Playgroud)

a只保证如果您将指针转换为其他类型,然后b返回原始类型,您将获得原始值.所以在下面:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);
Run Code Online (Sandbox Code Playgroud)

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

对于往返于*的铸造,c应该是首选.

  • 这实际上没有回答"何时使用reinterpret_cast"的问题. (47认同)
  • 当使用`reinterpret_cast`时,在C++ 11中不再指定`b`的值.在C++ 03中,使用`reinterpret_cast`禁止使用`int*`转换为`void*`(虽然编译器没有实现它,但这是不切实际的,因此对C++ 11进行了更改). (34认同)
  • 我喜欢"b"未定义的事实.它阻止你用它做傻事.如果你将某些东西投射到另一个指针类型,那么你就会遇到问题而且你不能依赖它会让你更加小心.如果您之前使用过static_cast <>,那么'b'的用途是什么? (17认同)
  • @LokiAstari我认为未指明并不能阻止你做傻事.当你记得它没有说明时,它只能阻止你.巨大的差异.我个人不喜欢未指定.记得太多了. (5认同)
  • 我认为reinterpret_cast <>保证了相同的位模式.(与指向另一种类型的有效指针不同). (3认同)

jwf*_*arn 152

reinterpret_cast必要的一种情况是与不透明数据类型进行交互时.这在程序员无法控制的供应商API中经常发生.这是一个人为的例子,供应商提供了一个用于存储和检索任意全局数据的API:

// vendor.hpp
typedef struct _Opaque * VendorGlobalUserData;
void VendorSetUserData(VendorGlobalUserData p);
VendorGlobalUserData VendorGetUserData();
Run Code Online (Sandbox Code Playgroud)

要使用此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;
}
Run Code Online (Sandbox Code Playgroud)

以下是示例API的设计实现:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
Run Code Online (Sandbox Code Playgroud)

  • @Xeo它们不使用void*因为它们在编译时丢失(某些)类型检查. (17认同)
  • 这可能是一个迟到的问题,但为什么供应商API不使用`void*`呢? (7认同)
  • 是的,这是我能想到的reinterpret_cast的唯一有意义的用法. (6认同)
  • "不透明"数据类型的实际用例是,当您希望将API公开给C但是用C++编写实现时.ICU是一个在几个地方执行此操作的库的示例.例如,在欺骗检查器API中,您处理类型为"USpoofChecker*"的指针,其中`USpoofChecker`是一个空结构.然而,在引擎盖下,无论何时传递一个`USpoofChecker*',它都会经历`reinterpret_cast`到内部C++类型. (4认同)
  • @jaskmar 不过,你可以 `reinterpret_cast` 指针。这就是这里的技巧:供应商的代码实际上从不尝试使用“VendorGlobalUserData”的值,它只是从用户代码中获取它并将其传回。并且用户代码确切地知道指针的正确类型是什么,因此它可以“reinterprete_cast”将其返回并访问已定义的原始对象。 (2认同)
  • @yeputons这就是为什么reinterpret_cast'ing`struct_a*-&gt;void*-&gt;struct_a*`定义良好的原因。另一方面,`struct_a*-&gt;void*-&gt;struct_b*` 和直接 `atruct_a-&gt;struct_b*` 则不然。 (2认同)

jas*_*mar 88

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

完整答案:

我们来考虑基本的数字类型.

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

另一方面,当你调用reinterpret_castCPU时不会调用任何计算.它只是处理内存中的一组位,就像它有另一种类型一样.所以,当你转换int*float*与此关键字,新的值(指针dereferecing之后)无关,在数学意义上的旧值.

示例: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;
}
Run Code Online (Sandbox Code Playgroud)

说明:x内存中的二进制表示可以是0000'0000'0000'0001(大)或0000'0001'0000'0000(小端).重新解释后,p指针下的字节可以分别为0000'00000000'0001.如果使用静态转换,0000'0001无论使用什么字节顺序,它都将始终如此.

  • 很好的例子!我会替换 uint16_t 的 short 和 uint8_t 的 unsigned char 以使其对人类不那么晦涩。 (3认同)
  • @JanTuroň 是的,我们不能假设 `short` 在内存中占用 16 位。更正。 (2认同)
  • 这个例子是错误的。constexpr 函数中不允许 reinterpret_cast (2认同)
  • 更具体地说,https://en.cppreference.com/w/cpp/language/constant_expression(项目16)明确指出,reinterpret_cast不能在常量表达式中使用。另请参阅https://github.com/cplusplus/draft/blob/master/papers/N3797.pdf(5.19常数表达式)第125-126页,其中明确排除了reinterpret_cast。然后* 7.1.5 constexpr说明符*项目5(第146页)*对于非模板,非默认constexpr函数...如果不存在参数值,则...可以是核心常量表达式的求值子表达式(5.19),**程序格式错误*** (2认同)

flo*_*din 18

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

对于您描述的情况,以及您可能考虑的任何情况reinterpret_cast,您可以使用static_cast或替代其他替代方案.除此之外,标准还有关于你可以期待什么的说法static_cast(§5.2.9):

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

因此,对于您的用例,标准化委员会似乎相当清楚,您打算使用它static_cast.

  • 大声笑,我怀疑reinterpret_crash可能确实会崩溃你的程序.但是reinterpret_cast不会.;) (53认同)
  • @paercebal`template <class T,U> T reinterpret_crash(U a){return*(T*)nullptr; }` (18认同)
  • 不是你的程序崩溃.该标准提供了一些关于reinterpret_cast的保证.只是没有人们通常期望的那么多. (5认同)
  • <irony>我在我的编译器上尝试过,不知何故,它拒绝编译`reinterpret_crash`.编译器错误绝不会阻止我崩溃我的重新解释程序.我会尽快报告错误!</ irony> (5认同)
  • 如果你使用得当的话就不会。也就是说,reinterpret_cast 从 A 到 B 再到 A 是完全安全且定义良好的。但 B 的值是未指定的,是的,如果你依赖它,可能会发生不好的事情。但是演员本身就足够安全,只要您仅按照标准允许的方式使用它即可。;) (2认同)

Ada*_*her 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;
}
Run Code Online (Sandbox Code Playgroud)

这最初是用C语言编写的,所以使用C类型转换,但类似的C++转换是reinterpret_cast.

  • 标准说这是未定义的行为:http://en.cppreference.com/w/cpp/language/reinterpret_cast(在“类型别名”下) (3认同)

TRP*_*RPh 5

这是 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);
}
Run Code Online (Sandbox Code Playgroud)

其输出结果如下:

#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);
}
Run Code Online (Sandbox Code Playgroud)

可以看出,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);

  • 除了最后一个例子之外,这里的所有内容都是未定义的行为;它仅作为说明语言实现细节的(不可靠)方法才有趣。 (2认同)