什么时候使用reinterpret_cast?


459

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

reinterpret_cast适用于两种情况:

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

我有点困惑的是我需要的一种用法,我从C调用C ++,C代码需要保留在C ++对象上,因此基本上它包含一个void*。应该使用哪种强制转换在void *和类型之间进行转换?

我已经看到了这两个的使用static_castreinterpret_cast?虽然从我一直阅读的内容来看static,由于转换可以在编译时进行,所以看起来更好。虽然它说用来reinterpret_cast从一种指针类型转换为另一种指针类型?


9
reinterpret_cast在运行时不会发生。它们都是编译时语句。来自en.cppreference.com/w/cpp/language/reinterpret_cast:“与static_cast不同,但与const_cast一样,reinterpret_cast表达式不会编译为任何CPU指令。它纯粹是一个编译器指令,它指示编译器对待位序列。 (对象表示形式),就像它具有类型new_type一样。”
Cris Luengo'3

@HeretoLearn,是否可以从* .c和* .cpp文件中添加相关代码段?我认为这可以改善问题的阐述。
OrenIshShalom

Answers:


442

C ++标准保证以下各项:

static_cast指向和指向的指针将void*保留地址。也就是说,在下面ab并且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*static_cast应该是首选。


18
我喜欢'b'未定义的事实。它阻止您使用它来做愚蠢的事情。如果将某些内容强制转换为其他指针类型,则您会提出问题,而您不能依靠它的事实使您更加小心。如果您在上面使用了static_cast <>,那么“ b”到底有什么用?
马丁·约克

3
我以为reinterpret_cast <>保证了相同的位模式。(这与指向另一种类型的有效指针不同)。
马丁·约克

37
b使用时,在C ++ 11中不再不再指定的值reinterpret_cast。并且在C ++ 03中int*void*禁止使用to强制转换reinterpret_cast(尽管编译器未实现,并且这是不切实际的,因此对C ++ 11进行了更改)。
约翰尼斯·绍布

55
这实际上并未回答“何时使用reinterpret_cast”的问题。
einpoklum

6
@LokiAstari我认为未指定并不会阻止您执行愚蠢的事情。当您记住未指定的内容时,它只会阻止您。差异很大。我个人不喜欢未指定的内容。记不清了。
Helin Wang

158

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

7
是的,那是我能想到的唯一有意义的reinterpret_cast用法。
jalf

8
这可能是一个较晚的问题,但是为什么供应商API不void*为此使用?
Xeo

19
@Xeo他们不使用void *,因为那样的话,它们会在编译时丢失(某些)类型检查。
jesup 2014年

4
一个“不透明”数据类型的实际使用案例是当您想向C公开API但用C ++编写实现时。ICU是在多个位置执行此操作的库的示例。例如,在欺骗检查器API中,您处理类型为的指针USpoofChecker*,其中USpoofChecker是空结构。但是,在幕后,每当传递a时USpoofChecker*,它都会经历reinterpret_cast内部C ++类型。
sffc

@sffc为什么不向用户公开C结构类型?
Gupta

101

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

完整答案:

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

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

另一方面,在调用时reinterpret_cast,CPU不会调用任何计算。它只是像对待其他类型一样对待内存中的一组位。所以,当你转换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;
}

说明:x内存中的二进制表示形式可能是0000'0000'0000'0001(big)或0000'0001'0000'0000(little endian)。重新解释广播后,p指针下的字节可以分别为0000'00000000'0001。如果您使用静态广播,则0000'0001无论使用哪种字节序,它始终为。

编辑:

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


1
很好的例子!我会用uint16_t的缩写和uint8_t的unsigned char代替,以减少对人类的混淆。
JanTuroň18年

@JanTuroň是的,我们不能假设这short需要16位内存。已更正。
jaskmar

1
这个例子是错误的。constexpr函数中不允许reinterpret_cast
Michael Veksler,

1
首先,最新的clang(7.0.0)和gcc(8.2.0)均拒绝此代码。不幸的是,我没有发现正式语言的局限性。所有我能找到的social.msdn.microsoft.com/Forums/vstudio/en-US/...
迈克尔Veksler

2
更具体地说,en.cppreference.com / w / cpp / language / constant_expression(项目16)明确指出reconstest_cast不能在常量表达式中使用。另请参阅github.com/cplusplus/draft/blob/master/papers/N3797.pdf(5.19 常数表达式)第125-126页,其中明确排除了reinterpret_cast。然后7.1.5 constexpr说明符第5项(第146页)*对于非模板,非默认constexpr函数...如果不存在任何参数值以致...可以是核心常量表达式的评估子表达式(5.19 ),则该程序
格式

20

reinterpret_castC ++标准未定义的含义。因此,理论上a reinterpret_cast可能会使您的程序崩溃。在实践中,编译器会尝试执行您期望的操作,这就是将要传递的内容解释为它们是要转换的类型。如果您知道将要使用的编译器可以做什么,则reinterpret_cast 可以使用它,但是要说它是可移植的就行了。

对于您描述的情况,几乎可以考虑reinterpret_cast使用的任何情况,都可以使用static_cast或其他替代方法。除其他事项外,标准还说了您对static_cast(§5.2.9)的期望:

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

因此,对于您的用例,标准化委员会打算供您使用似乎很清楚static_cast


5
不太会使您的程序崩溃。该标准为reinterpret_cast提供了一些保证。只是没有人们通常期望的那么多。
jalf

1
如果使用正确,则不会。也就是说,从A到B再到A的reinterpret_cast是完全安全且定义明确的。但是B的值是不确定的,是的,如果您依靠它,可能会发生不好的事情。但是只要您仅按照标准允许的方式使用演员表,演员表本身就足够安全。;)
jalf

55
大声笑,我怀疑reinterpret_crash确实可能会使您的程序崩溃。但是reinterpret_cast不会。;)
jalf

5
<irony>我在编译器上尝试过它,但是不知何故,它拒绝编译reinterpret_crash。编译器错误无法阻止我重新解释程序。我将尽快报告一个错误!</ irony>
paercebal 2010年

18
@paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }

12

reinterpret_cast的一种用法是是否要对(IEEE 754)浮点数应用按位运算。一个例子是快速逆平方根技巧:

https://zh.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。


1
error: invalid cast of an rvalue expression of type 'int64_t {aka long long int}' to type 'double&' reinterpret_cast<double&>((reinterpret_cast<int64_t&>(d) >> 1) + (1L << 61))- ideone.com/6S4ijc
Orwellophile

1
标准说这是未定义的行为:en.cppreference.com/w/cpp/language/reinterpret_cast(在“类型别名”下)
Cris Luengo

所有@CrisLuengo如果我代替reinterpret_castmemcpy,它仍然UB?
桑索德(Sandthorn)

@sandthorn:根据标准,这是UB,但如果它适用于您的体系结构,请不要担心。我猜想,对于任何用于英特尔架构的编译器,这个技巧都是可以的。它不能在其他架构上按预期方式工作(甚至崩溃),例如,浮点数和long可能存储在单独的内存仓中(我不知道任何这样的架构,这只是一个参数而已...) 。memcpy肯定会使其合法。
Cris Luengo


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

我试图总结并使用模板编写了一个简单的安全类型转换。请注意,此解决方案不能保证将指针强制转换为函数。


1
什么?何必呢?这正是reinterpret_cast在这种情况下已经做的事情:“可以将对象指针显式转换为其他类型的对象指针。[72]当将对象指针类型的prvalue v转换为对象指针类型“ pointer to cv T ”时,结果是static_cast<cv T*>(static_cast<cv void*>(v))。” -N3797。
underscore_d

至于c++2003标准,我可以发现,reinterpret_caststatic_cast<cv T*>(static_cast<cv void*>(v))
萨沙Zezulinsky

1
好的,是的,但是我不在乎13年前的版本,大多数编码人员是否(如果有可能)也可以避免使用它。除非另有说明,否则答案和评论应真正反映最新的可用标准……恕我直言。无论如何,我想委员会认为有必要在2003年之后明确添加它。(因为IIRC,在C ++ 11中是相同的)
underscore_d

C++03此之前C++98。大量项目使用的是旧的C ++而不是可移植的C。有时您必须关心可移植性。例如,您必须在Solaris,AIX,HPUX,Windows上支持相同的代码。对于编译器的依赖性和可移植性,这很棘手。因此,引入便携地狱的一个很好的例子是使用reinterpret_cast在你的代码
萨沙Zezulinsky

再次,如果像我一样,您乐于将自己限制在仅能与该语言的最新版本和最出色版本配合使用的平台上,那么您的反对意见是有争议的。
underscore_d

1

首先,您具有一些特定类型的数据,例如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

简介:这意味着将相同的内存用作不同的类型。因此,您可以将浮点数的二进制表示形式转换为浮点数,如上述的int类型。例如,0x80000000是-0(尾数和指数为null,但符号msb为1。这也适用于双打和长双打。

优化:我认为reinterpret_cast将在许多编译器中得到优化,而c广播是由指针算术进行的(该值必须复制到内存中,因为指针无法指向cpu寄存器)。

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

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

确实,“这意味着将相同的内存用作不同的类型”,但是它仅限于特定的一对类型。在您的示例reinterpret_cast表单中,intto float&是未定义的行为。
jaskmar

1

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

例如,使用以下代码:

#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- 有关最后3个信息,请参阅godbolt),结果static_castreinterpret_cast2 的结果之差(对于MSVC为4)。唯一警告差异的编译器是clang,具有:

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

最后一个需要注意的是,如果基类没有任何数据成员(例如int i;)然后铛,GCC和ICC为返回相同的地址reinterpret_caststatic_cast,而MSVC还没有。


1

这是Avi Ginsburg程序的一种变体,它清楚地说明了reinterpret_castChris 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.

可以看出,B对象首先作为特定于B的数据内置在内存中,然后是嵌入的A对象。的static_cast正确返回嵌入对象的地址,并且通过所创建的指针static_cast正确地给出数据字段的值。由生成的指针reinterpret_cast将其b存储位置视为一个普通的A对象,因此,当指针尝试获取数据字段时,它将返回一些特定于B的数据,就像该字段的内容一样。

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

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


-6

快速解答:static_cast如果编译可使用,否则请求助reinterpret_cast


-16

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

在C ++中,void *无需任何强制转换就可以将指向对象的指针转换为该指针。但这反过来不是真的。您需要a static_cast来恢复原始指针。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.