何时使用reinterpret_cast?

When to use reinterpret_cast?

提问人:HeretoLearn 提问时间:2/22/2009 最后编辑:SisirHeretoLearn 更新时间:4/7/2023 访问量:473596

问:

我对 vs 的适用性有点困惑。从我所读到的内容来看,一般规则是当类型可以在编译时解释时使用静态强制转换,因此这个词.这也是 C++ 编译器在内部用于隐式强制转换的强制转换。reinterpret_caststatic_caststatic

reinterpret_casts 适用于两种场景:

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

我有点困惑的地方是我需要的一个用法,我从 C 调用 C++,C 代码需要保留 C++ 对象,所以基本上它包含一个 .应该使用什么强制转换来转换 和 Class 类型?void*void *

我见过两者的用法 和 ?虽然从我一直在阅读的内容来看,它似乎更好,因为投射可以在编译时发生?虽然它说用于从一种指针类型转换为另一种指针类型?static_castreinterpret_caststaticreinterpret_cast

C++ 转换 reinterpret-cast

评论

25赞 Cris Luengo 3/4/2017
reinterpret_cast在运行时不会发生。它们都是编译时语句。来自 en.cppreference.com/w/cpp/language/reinterpret_cast:“与 static_cast 不同,但与 const_cast 不同,reinterpret_cast表达式不会编译为任何 CPU 指令。它纯粹是一个编译器指令,它指示编译器将表达式的位序列(对象表示)视为具有 new_type 类型。
0赞 OrenIshShalom 11/16/2017
@HeretoLearn,是否可以从 *.c 和 *.cpp 文件中添加相关代码段?我认为它可以改善问题的阐述。

答:

-20赞 dirkgently 2/22/2009 #1

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

在 C++ 中,指向对象的指针可以在没有任何强制转换的情况下转换为。但反过来就不是这样了。您需要一个来取回原始指针。void *static_cast

21赞 flodin 2/22/2009 #2

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

对于您描述的情况,以及您可能考虑的几乎任何情况,您可以使用或其他一些替代方法。除其他事项外,该标准还说明了您可以期待的内容 (§5.2.9):reinterpret_caststatic_caststatic_cast

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

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

评论

7赞 jalf 2/22/2009
没有完全崩溃你的程序。该标准提供了一些关于reinterpret_cast的保证。只是没有人们经常预期的那么多。
2赞 jalf 2/22/2009
如果你使用得当,就不会。也就是说,从 A 到 B 再到 A 的reinterpret_cast是完全安全且定义明确的。但是 B 的值是未指定的,是的,如果你依赖它,可能会发生坏事。但是铸件本身就足够安全,只要你只按照标准允许的方式使用它。;)
61赞 jalf 2/22/2009
哈哈,我怀疑reinterpret_crash确实可能会使您的程序崩溃。但reinterpret_cast不会。;)
7赞 paercebal 7/11/2010
<具有讽刺意味>我在我的编译器上尝试了它,不知何故,它拒绝编译.编译器错误绝不会阻止我崩溃我的重新解释程序。我会尽快报告一个错误!</讽刺>reinterpret_crash
20赞 1/14/2012
@paercebaltemplate<class T, U> T reinterpret_crash(U a) { return *(T*)nullptr; }
563赞 jalf 2/22/2009 #3

C++ 标准保证以下几点:

static_cast对指向和从的指针进行操作会保留地址。也就是说,在以下 中,和 都指向同一个地址: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);

a并包含相同的值,但 的值未指定。(在实践中,它通常包含与 和 相同的地址,但标准中没有指定,并且在具有更复杂内存系统的计算机上可能并非如此。cbac

对于来回的铸造,应优先选择。void*static_cast

评论

18赞 Martin York 2/22/2009
我喜欢“b”未定义的事实。它可以阻止你用它做傻事。如果你把某些东西投射到另一个指针类型上,你就是在问问题,而你不能依赖它的事实使你更加小心。如果你用过上面static_cast<>那么“b”到底有什么用?
5赞 Martin York 2/22/2009
我认为reinterpret_cast<>保证了相同的位模式。(这与指向另一种类型的有效指针不同)。
49赞 Johannes Schaub - litb 10/29/2011
使用 时,在 C++11 中不再未指定 的值。在 C++03 中,禁止使用 to 强制转换(尽管编译器没有实现这一点并且这是不切实际的,因此针对 C++11 进行了更改)。breinterpret_castint*void*reinterpret_cast
120赞 einpoklum 4/16/2016
这实际上并没有回答“何时使用reinterpret_cast”的问题。
11赞 Helin Wang 1/30/2017
@LokiAstari我认为未指明并不能阻止你做傻事。只有当你记得它未指定时,它才会阻止你。巨大的差异。就我个人而言,我不喜欢未指定的。太多了,记不住了。
212赞 jwfearn 2/22/2009 #4

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

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

要使用此 API,程序员必须将其数据转换回并返回。 行不通,必须使用:VendorGlobalUserDatastatic_castreinterpret_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; }

评论

9赞 jalf 2/22/2009
是的,这是我能想到的对reinterpret_cast唯一有意义的用途。
13赞 Xeo 10/29/2011
这可能是一个迟到的问题,但为什么供应商 API 不使用它呢?void*
29赞 jesup 8/14/2014
@Xeo 他们不使用 void *,因为那样他们会在编译时丢失(一些)类型检查。
4赞 sffc 3/23/2017
“不透明”数据类型的一个实际用例是,当您想要向 C 公开 API 但用 C++ 编写实现时。ICU 是在多个地方执行此操作的库的一个示例。例如,在欺骗检查器 API 中,您处理 类型的指针,其中为空结构。但是,在引擎盖下,每当您传递 时,它都会经历内部 C++ 类型。USpoofChecker*USpoofCheckerUSpoofChecker*reinterpret_cast
2赞 Mariusz Jaskółka 10/22/2020
@yeputons这就是为什么reinterpret_cast'ing定义明确的原因。另一方面,直接不是。struct_a*->void*->struct_a*struct_a*->void*->struct_b*atruct_a->struct_b*
1赞 Sasha Zezulinsky 1/18/2013 #5
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

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

评论

2赞 underscore_d 1/8/2016
什么?何苦?这正是在这种情况下已经执行的操作:“对象指针可以显式转换为不同类型的对象指针。[72] 当对象指针类型的 prvalue 转换为对象指针类型“指向 cv 的指针”时,结果为 .“ -- N3797。reinterpret_castvTstatic_cast<cv T*>(static_cast<cv void*>(v))
0赞 Sasha Zezulinsky 1/12/2016
至于标准,我找不到c++2003reinterpret_caststatic_cast<cv T*>(static_cast<cv void*>(v))
1赞 underscore_d 1/16/2016
好吧,没错,但我不在乎 13 年前的版本,如果(很可能)他们可以避免它,大多数编码人员也不应该。除非另有说明,否则答案和评论应真正反映最新的可用标准......恕我直言。无论如何,我想委员会认为有必要在2003年之后明确增加这一点。(因为 IIRC,在 C++11 中也是如此)
1赞 Sasha Zezulinsky 1/17/2016
在此之前是.大量项目使用旧的 C++ 而不是可移植的 C。有时你必须关心便携性。例如,您必须在 Solaris、AIX、HPUX 和 Windows 上支持相同的代码。在编译器依赖性和可移植性方面,这很棘手。因此,引入可移植性地狱的一个很好的例子是在代码中使用C++03C++98reinterpret_cast
0赞 underscore_d 1/17/2016
同样,如果像我一样,你乐于将自己限制在那些与最新和最伟大的语言版本配合良好的平台上,那么你的反对意见是一个有争议的问题。
3赞 Martin R. 10/30/2014 #6

可以使用reinterprete_cast在编译时检查继承。
请看这里:在编译时使用 reinterpret_cast 检查继承

11赞 Adam P. Goucher 1/13/2016 #7

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。

评论

1赞 Orwellophile 7/10/2016
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
3赞 Cris Luengo 3/4/2017
该标准指出,这是未定义的行为:en.cppreference.com/w/cpp/language/reinterpret_cast(在“类型别名”下)
0赞 sandthorn 7/17/2018
@CrisLuengo 如果我用 全部替换,它仍然是 UB 吗?reinterpret_castmemcpy
1赞 Cris Luengo 7/17/2018
@sandthorn:根据标准,这是UB,但如果它适用于你的架构,请不要担心。我认为,这个技巧对于任何英特尔架构的编译器来说都是可以的。在其他架构上,它无法按预期工作(甚至崩溃)——例如,浮点数和多头数可能存储在单独的内存区间中(我不知道任何这样的架构,这只是一个参数...... 肯定会让它合法化。memcpy
-9赞 Marius K 11/10/2016 #8

快速回答:如果它编译,请使用,否则求助于 .static_castreinterpret_cast

2赞 cmdLP 2/23/2017 #9

首先,这里有一些特定类型的数据,例如 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__; })

评论

1赞 Mariusz Jaskółka 12/19/2019
诚然,“这意味着相同的内存被用作不同的类型”,但它仅限于特定的类型对。在您的示例表单中,to 是未定义的行为。reinterpret_castintfloat&
0赞 Davis Herring 8/18/2020
编译器在可能的情况下优化为纯粹的寄存器操作;强制转换很容易(但也是 UB - 如果使用该值 - 正如本页所指出的那样)。memcpy
192赞 Mariusz Jaskółka 4/7/2017 #10

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

完整答案:

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

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

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

请注意,在reinterprt_cast后读取或修改值通常是未定义的行为。在大多数情况下,如果要实现某些数据的位表示,则应使用指针或引用(从 C++17 开始),这几乎总是合法的操作。其他“安全”类型是 和 ,但我想说它不应该在现代 C++ 中用于此目的,因为它具有更好的语义。std::bytecharunsigned charstd::byte

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

/*constexpr*/ bool is_little_endian() {
  std::uint16_t x=0x0001;
  auto p = reinterpret_cast<std::uint8_t*>(&x);
  return *p != 0;
}

解释:内存中的二进制表示可以是 (big) 或 (little endian)。重新解释转换后,指针下的字节可以分别是 或 。如果使用 static-casting,则无论使用什么字节序,它都将始终为 。x0000'0000'0000'00010000'0001'0000'0000p0000'00000000'00010000'0001

编辑:

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

评论

4赞 Jan Turoň 8/27/2018
很好的例子!我会替换 uint16_t 的缩写和 uint8_t 的无符号 char,以使其对人类来说不那么晦涩难懂。
3赞 Mariusz Jaskółka 8/28/2018
@JanTuroň,我们不能假设它在内存中占用 16 位。纠正。short
3赞 Michael Veksler 3/2/2019
这个例子是错误的。constexpr 函数中不允许reinterpret_cast
1赞 Michael Veksler 3/3/2019
首先,这个代码被最新的clang(7.0.0)和gcc(8.2.0)都拒绝了。不幸的是,我没有发现正式语言的局限性。我能找到的只有 social.msdn.microsoft.com/Forums/vstudio/en-US/......
3赞 Michael Veksler 3/3/2019
更具体地说,en.cppreference.com/w/cpp/language/constant_expression(第 16 项)明确指出 reinterpret_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) 的计算子表达式,程序格式不正确 *
5赞 TRPh 10/24/2019 #11

这是 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 对象。正确地返回嵌入的 A 对象的地址,由 创建的指针正确地给出数据字段的值。生成的指针将 的内存位置视为一个普通的 A 对象,因此当指针尝试获取数据字段时,它会返回一些特定于 B 的数据,就好像它是该字段的内容一样。static_caststatic_castreinterpret_castb

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

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

评论

2赞 Davis Herring 8/18/2020
除了最后一个示例之外,这里的一切都是未定义的行为;它仅作为说明语言实现细节的(不可靠)手段才有意思。