ChatGPT解决这个技术问题 Extra ChatGPT

何时使用 reinterpret_cast?

我对 reinterpret_caststatic_cast 的适用性有点混淆。根据我的阅读,一般规则是当类型可以在编译时解释时使用静态转换,因此使用 static 一词。这也是 C++ 编译器在内部用于隐式转换的转换。

reinterpret_cast 适用于两种场景:

将整数类型转换为指针类型,反之亦然

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

我有点困惑的是我需要的一种用法,我从 C 调用 C++,并且 C 代码需要保留 C++ 对象,所以基本上它包含 void*。在 void * 和 Class 类型之间转换应该使用什么类型转换?

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

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

l
leiyc

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

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

对于与 void* 之间的转换,应首选 static_cast


我喜欢'b'未定义的事实。它可以阻止你用它做愚蠢的事情。如果您将某些内容转换为另一种指针类型,那么您就是在提出问题,而您不能依赖它的事实使您更加小心。如果您在上面使用了 static_cast<> ,那么“b”有什么用处?
我认为 reinterpret_cast<> 保证了相同的位模式。 (这与指向另一种类型的有效指针不同)。
使用 reinterpret_cast 时,在 C++11 中不再未指定 b 的值。并且在 C++03 中,禁止使用 reinterpret_castint* 转换为 void*(尽管编译器没有实现它并且它不切实际,因此在 C++11 中进行了更改)。
这实际上并没有回答“何时使用 reinterpret_cast”的问题。
@LokiAstari 我认为未指定并不会阻止您做愚蠢的事情。只有当您记得它未指定时,它才会阻止您。巨大的差异。我个人不喜欢未指明的。太多记不住了。
j
jwfearn

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

是的,这就是我能想到的唯一有意义的 reinterpret_cast 用法。
这可能是一个迟到的问题,但为什么供应商 API 不为此使用 void*
@Xeo他们不使用 void * 因为那样他们会在编译时丢失(一些)类型检查。
“不透明”数据类型的一个实际用例是当您想向 C 公开 API 但用 C++ 编写实现时。 ICU 是一个在多个地方执行此操作的库的示例。例如,在欺骗检查器 API 中,您处理类型为 USpoofChecker* 的指针,其中 USpoofChecker 是一个空结构。但是,在后台,每当您传递 USpoofChecker* 时,它都会将 reinterpret_cast 传递给内部 C++ 类型。
@yeputons 这就是 reinterpret_cast'ing struct_a*->void*->struct_a* 定义明确的原因。另一方面,struct_a*->void*->struct_b* 和直接 atruct_a->struct_b* 不是。
M
Mariusz Jaskółka

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

完整答案:

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

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

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

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

示例: 确实,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 编译器拒绝编译它(这是正确的)。


很好的例子!我会替换 uint16_t 的 short 和 uint8_t 的 unsigned char ,以使其对人类来说不那么晦涩难懂。
@JanTuroň 是的,我们不能假设 short 在内存中占用 16 位。已更正。
这个例子是错误的。 constexpr 函数中不允许 reinterpret_cast
首先,此代码被最新的 clang (7.0.0) 和 gcc (8.2.0) 拒绝。不幸的是,我没有发现正式语言的限制。我只能找到social.msdn.microsoft.com/Forums/vstudio/en-US/…
更具体地说,en.cppreference.com/w/cpp/language/constant_expression(第 16 项)明确指出 reinterpret_cast 不能用于常量表达式。另请查看 github.com/cplusplus/draft/blob/master/papers/N3797.pdf(5.19 常量表达式)pages125-126,它明确排除了 reinterpret_cast。然后 7.1.5 constexpr 说明符 第 5 项(第 146 页) *对于非模板、非默认 constexpr 函数...如果不存在任何参数值使得 ... 可以是评估的子表达式对于核心常量表达式 (5.19),程序格式错误 *
f
flodin

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

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

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

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


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

reinterpret_cast 的一种用途是,如果您想对 (IEEE 754) 浮点数应用按位运算。一个例子是快速平方根逆技巧:

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

它将浮点数的二进制表示视为整数,将其右移并从常数中减去,从而将指数减半并取反。转换回浮点数后,对其进行 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。


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
标准说这是未定义的行为:en.cppreference.com/w/cpp/language/reinterpret_cast(在“类型别名”下)
@CrisLuengo 如果我用 memcpy 替换所有 reinterpret_cast,它仍然是 UB 吗?
@sandthorn:根据标准,这是 UB,但如果它适用于您的架构,请不要担心。我想这个技巧对于英特尔架构的任何编译器都是可以的。它无法在其他架构上按预期工作(甚至崩溃)——例如,浮点数和长整数可能存储在单独的内存隔间中(我不知道任何这样的架构,这只是一个论点......) . memcpy 肯定会使其合法化。
I
Intrastellar Explorer

这是 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 特定数据,就好像它是 this 的内容一样场地。

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

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


除了最后一个例子,这里的一切都是未定义的行为;它只是作为一种(不可靠的)说明语言实现细节的方法才有趣。
C
Community

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


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

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


什么?何必?这正是 reinterpret_cast 在这种情况下所做的:“对象指针可以显式转换为不同类型的对象指针。[72] 当对象指针类型的 prvalue v转换为对象指针类型“pointer to cv T”,结果为static_cast<cv T*>(static_cast<cv void*>(v))。 ——N3797。
至于 c++2003 标准,我可以 NOT 发现 reinterpret_cast 确实 static_cast<cv T*>(static_cast<cv void*>(v))
好的,没错,但我不关心 13 年前的版本,大多数编码人员也不应该关心(可能)他们可以避免它。除非另有说明,否则答案和评论应该真正反映最新的可用标准......恕我直言。无论如何,我猜委员会认为有必要在 2003 年之后明确添加这个。(因为 IIRC,在 C++11 中也是如此)
C++03 之前是 C++98。大量项目使用旧的 C++ 而不是可移植的 C。有时您必须关心可移植性。例如,您必须在 Solaris、AIX、HPUX、Windows 上支持相同的代码。在编译器依赖性和可移植性方面,它是棘手的。因此,引入可移植性地狱的一个很好的例子是在您的代码中使用 reinterpret_cast
再说一次,如果你像我一样乐于将自己限制在与最新最好的语言版本配合得很好的平台上,那么你的反对就是一个有争议的问题。
c
cmdLP

首先,您在此处有一些特定类型的数据,例如 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(尾数和指数为空,但符号 msb 为一。这也适用于双精度数和长双精度数。

优化:我认为 reinterpret_cast 会在许多编译器中进行优化,而 c-casting 是通过指针算法进行的(必须将值复制到内存中,因为指针不能指向 cpu- 寄存器)。

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

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

确实“这意味着相同的内存被用作不同的类型”,但它仅限于特定的类型对。在您的示例 reinterpret_cast 中,从 intfloat& 是未定义的行为。
编译器优化 memcpy 以尽可能纯粹地注册操作;演员表很容易(但也是 UB - 如果值是 used - 正如本页所指出的那样)。
M
MD XF

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


M
MD XF

阅读FAQ!在 C 中保存 C++ 数据可能是有风险的。

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