何时使用reinterpret_cast?

发布于 2024-07-13 13:56:39 字数 595 浏览 9 评论 0原文

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

reinterpret_cast适用于两种场景:

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

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

我见过 static_castreinterpret_cast 的用法? 尽管从我读到的内容来看,静态似乎更好,因为转换可以在编译时发生? 虽然它说使用reinterpret_cast从一种指针类型转换为另一种指针类型?

I am little confused with the applicability of reinterpret_cast vs static_cast. From what I have read the general rules are to use static cast when the types can be interpreted at compile time hence the word static. This is the cast the C++ compiler uses internally for implicit casts also.

reinterpret_casts are applicable in two scenarios:

  • convert integer types to pointer types and vice versa
  • convert one pointer type to another. The general idea I get is this is unportable and should be avoided.

Where I am a little confused is one usage which I need, I am calling C++ from C and the C code needs to hold on to the C++ object so basically it holds a void*. What cast should be used to convert between the void * and the Class type?

I have seen usage of both static_cast and reinterpret_cast? Though from what I have been reading it appears static is better as the cast can happen at compile time? Though it says to use reinterpret_cast to convert from one pointer type to another?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

暖阳 2024-07-20 13:56:39

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

The C++ standard guarantees the following:

static_casting a pointer to and from void* preserves the address. That is, in the following, a, b and c all point to the same address:

int* a = new int();
void* b = static_cast<void*>(a);
int* c = static_cast<int*>(b);

reinterpret_cast only guarantees that if you cast a pointer to a different type, and then reinterpret_cast it back to the original type, you get the original value. So in the following:

int* a = new int();
void* b = reinterpret_cast<void*>(a);
int* c = reinterpret_cast<int*>(b);

a and c contain the same value, but the value of b is unspecified. (in practice it will typically contain the same address as a and c, but that's not specified in the standard, and it may not be true on machines with more complex memory systems.)

For casting to and from void*, static_cast should be preferred.

我不在是我 2024-07-20 13:56:39

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

One case when reinterpret_cast is necessary is when interfacing with opaque data types. This occurs frequently in vendor APIs over which the programmer has no control. Here's a contrived example where a vendor provides an API for storing and retrieving arbitrary global data:

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

To use this API, the programmer must cast their data to VendorGlobalUserData and back again. static_cast won't work, one must use 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;
}

Below is a contrived implementation of the sample API:

// vendor.cpp
static VendorGlobalUserData g = 0;
void VendorSetUserData(VendorGlobalUserData p) { g = p; }
VendorGlobalUserData VendorGetUserData() { return g; }
夏九 2024-07-20 13:56:39

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

完整答案:

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

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

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

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

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

The short answer:
If you don't know what reinterpret_cast stands for, don't use it. If you will need it in the future, you will know.

Full answer:

Let's consider basic number types.

When you convert for example int(12) to float (12.0f) your processor needs to invoke some calculations as both numbers have different bit representation. This is what static_cast stands for.

On the other hand, when you call reinterpret_cast the CPU does not invoke any calculations. It just treats a set of bits in the memory like if it had another type. So when you convert int* to float* with this keyword, the new value (after pointer dereferecing) has nothing to do with the old value in mathematical meaning (ignoring the fact that it is undefined behavior to read this value).

Be aware that reading or modifying values after reinterprt_cast'ing are very often Undefined Behavior. In most cases, you should use pointer or reference to std::byte (starting from C++17) if you want to achieve the bit representation of some data, it is almost always a legal operation. Other "safe" types are char and unsigned char, but I would say it shouldn't be used for that purpose in modern C++ as std::byte has better semantics.

Example: It is true that reinterpret_cast is not portable because of one reason - byte order (endianness). But this is often surprisingly the best reason to use it. Let's imagine the example: you have to read binary 32bit number from file, and you know it is big endian. Your code has to be generic and has to work properly on big endian (e.g. some ARM) and little endian (e.g. x86) systems. So you have to check the byte order. It is well-known on compile time so you can write constexpr function: You can write a function to achieve this:

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

Explanation: the binary representation of x in memory could be 0000'0000'0000'0001 (big) or 0000'0001'0000'0000 (little endian). After reinterpret-casting the byte under p pointer could be respectively 0000'0000 or 0000'0001. If you use static-casting, it will always be 0000'0001, no matter what endianness is being used.

EDIT:

In the first version I made example function is_little_endian to be constexpr. It compiles fine on the newest gcc (8.3.0) but the standard says it is illegal. The clang compiler refuses to compile it (which is correct).

凯凯我们等你回来 2024-07-20 13:56:39

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

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

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

因此,对于您的用例,标准化委员会似乎很清楚地希望您使用static_cast

The meaning of reinterpret_cast is not defined by the C++ standard. Hence, in theory a reinterpret_cast could crash your program. In practice compilers try to do what you expect, which is to interpret the bits of what you are passing in as if they were the type you are casting to. If you know what the compilers you are going to use do with reinterpret_cast you can use it, but to say that it is portable would be lying.

For the case you describe, and pretty much any case where you might consider reinterpret_cast, you can use static_cast or some other alternative instead. Among other things the standard has this to say about what you can expect of static_cast (§5.2.9):

An rvalue of type “pointer to cv void” can be explicitly converted to a pointer to object type. A value of type pointer to object converted to “pointer to cv void” and back to the original pointer type will have its original value.

So for your use case, it seems fairly clear that the standardization committee intended for you to use static_cast.

南街女流氓 2024-07-20 13:56:39

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。

One use of reinterpret_cast is if you want to apply bitwise operations to (IEEE 754) floats. One example of this was the Fast Inverse Square-Root trick:

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

It treats the binary representation of the float as an integer, shifts it right and subtracts it from a constant, thereby halving and negating the exponent. After converting back to a float, it's subjected to a Newton-Raphson iteration to make this approximation more exact:

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

This was originally written in C, so uses C casts, but the analogous C++ cast is the reinterpret_cast.

放低过去 2024-07-20 13:56:39

这是 Avi Ginsburg 程序的一个变体,它清楚地说明了 Chris Luengo、flodin 和 cmdLP 提到的 reinterpret_cast 的属性:编译器将指向的内存位置视为对象new type:

#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(&i);

Here is a variant of Avi Ginsburg's program which clearly illustrates the property of reinterpret_cast mentioned by Chris Luengo, flodin, and cmdLP: that the compiler treats the pointed-to memory location as if it were an object of the new type:

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

Which results in output like this:

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.

It can be seen that the B object is built in memory as B-specific data first, followed by the embedded A object. The static_cast correctly returns the address of the embedded A object, and the pointer created by static_cast correctly gives the value of the data field. The pointer generated by reinterpret_cast treats b's memory location as if it were a plain A object, and so when the pointer tries to get the data field it returns some B-specific data as if it were the contents of this field.

One use of reinterpret_cast is to convert a pointer to an unsigned integer (when pointers and unsigned integers are the same size):

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

风轻花落早 2024-07-20 13:56:39

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

You could use reinterprete_cast to check inheritance at compile time.
Look here:
Using reinterpret_cast to check inheritance at compile time

那支青花 2024-07-20 13:56:39

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

Brief 之间做出决定:这意味着相同的内存被用作不同的类型。 因此,您可以将浮点数的二进制表示形式(如上面的 int 类型)转换为浮点数。 例如,0x80000000 是 -0(尾数和指数为空,但符号 MSB 为 1。这也适用于双精度数和长双精度数。

优化:我认为在许多编译器中,reinterpret_cast 会得到优化,而 c 转换是由pointerarithmetic创建(该值必须复制到内存,因为指针无法指向cpu寄存器)

注意:在这两种情况下,您都应该在转换之前将转换后的值保存在变量中!

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

First you have some data in a specific type like int here:

int x = 0x7fffffff://==nan in binary representation

Then you want to access the same variable as an other type like float:
You can decide between

float y = reinterpret_cast<float&>(x);

//this could only be used in cpp, looks like a function with template-parameters

or

float y = *(float*)&(x);

//this could be used in c and cpp

BRIEF: it means that the same memory is used as a different type. So you could convert binary representations of floats as int type like above to floats. 0x80000000 is -0 for example (the mantissa and exponent are null but the sign, the msb, is one. This also works for doubles and long doubles.

OPTIMIZE: I think reinterpret_cast would be optimized in many compilers, while the c-casting is made by pointerarithmetic (the value must be copied to the memory, cause pointers couldn't point to cpu- registers).

NOTE: In both cases you should save the casted value in a variable before cast! This macro could help:

#define asvar(x) ({decltype(x) __tmp__ = (x); __tmp__; })
浮萍、无处依 2024-07-20 13:56:39
template <class outType, class inType>
outType safe_cast(inType pointer)
{
    void* temp = static_cast<void*>(pointer);
    return static_cast<outType>(temp);
}

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

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

I tried to conclude and wrote a simple safe cast using templates.
Note that this solution doesn't guarantee to cast pointers on a functions.

百善笑为先 2024-07-20 13:56:39

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

Quick answer: use static_cast if it compiles, otherwise resort to reinterpret_cast.

-黛色若梦 2024-07-20 13:56:39

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

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

Read the FAQ! Holding C++ data in C can be risky.

In C++, a pointer to an object can be converted to void * without any casts. But it's not true the other way round. You'd need a static_cast to get the original pointer back.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文