reinterpret_cast 转换

来自cppreference.com
< cpp‎ | language
 
 
 
表达式
概述
值类别(左值 lvalue、右值 rvalue、亡值 xvalue)
求值顺序(序列点)
常量表达式
潜在求值表达式
初等表达式
lambda 表达式(C++11)
字面量
整数字面量
浮点字面量
布尔字面量
字符字面量,包含转义序列
字符串字面量
空指针字面量(C++11)
用户定义字面量(C++11)
运算符
赋值运算符a=ba+=ba-=ba*=ba/=ba%=ba&=ba|=ba^=ba<<=ba>>=b
自增与自减++a--aa++a--
算术运算符+a-aa+ba-ba*ba/ba%b~aa&ba|ba^ba<<ba>>b
逻辑运算符a||ba&&b!a
比较运算符a==ba!=ba<ba>ba<=ba>=ba<=>b(C++20)
成员访问运算符a[b]*a&aa->ba.ba->*ba.*b
其他运算符a(...)a,ba?b:c
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
折叠表达式(C++17)
运算符的代用表示
优先级和结合性
运算符重载
默认比较(C++20)
类型转换
隐式转换
一般算术转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)a, T(a)
用户定义转换
 

通过重新解释底层位模式在类型间转换。

语法

reinterpret_cast< 新类型 >( 表达式 )

返回 新类型 类型的值。

解释

static_cast 不同,但与 const_cast 类似,reinterpret_cast 表达式不会编译成任何 CPU 指令(除非在整数和指针间转换,或在指针表示依赖它的类型的不明架构上)。它纯粹是一个编译时指令,指示编译器将 表达式 视为如同具有 新类型 类型一样处理。

只有下列转换在不去除常量性 或易变性 的场合才能用 reinterpret_cast 执行。

1) 整型、枚举、指针或成员指针类型的表达式可以转换到它自身的类型。产生的值与 表达式 的相同。
2) 指针能转换到大小足以保有它的类型的所有值的任何整型类型(例如转换到 std::uintptr_t
3) 任何整型或枚举类型的值都可以转换到指针类型。指针转换到有足够大小的整数再转换回同一指针类型后,保证拥有它原来的值,否则结果指针无法安全地解引用(不保证相反方向的往返转换;相同的指针可以拥有多种整数表示)。不保证空指针常量 NULL 或整数零生成目标类型的空指针值;此时应该用 static_cast隐式转换
4) 任何 std::nullptr_t 类型的值,包含 nullptr,都可以转换到任何整型类型,如同它是 (void*)0 一样。但没有值能转换到 std::nullptr_t,甚至 nullptr 也不行:此时应该用 static_cast
(C++11 起)
5) 任何对象指针类型 T1* 都可以转换到指向对象指针类型 cv T2*。这严格等价于 static_cast<cv T2*>(static_cast<cv void*>(表达式))(这意味着,如果 T2 的对齐要求不比 T1 的更严格,那么指针的值不会改变,且将结果指针转换回原类型将生成它原来的值)。任何情况下,只有类型别名化(type aliasing)规则允许(见下文)时,结果指针才可以安全地解引用。
6) T1 类型的左值 (C++11 前)泛左值 (C++11 起)表达式可转换成到另一个类型 T2 的引用。结果与 *reinterpret_cast<T2*>(p) 的结果相同,其中 p 是类型为“指向 T1 的指针”的指向 表达式 所指的对象的指针。不创建临时量,不进行复制,不调用构造函数或转换函数。只有类型别名化(type aliasing)规则允许(见下文)时,结果引用才可以安全访问。
7) 任何函数指针都可以转换成指向不同函数类型的指针。通过指向不同函数类型的指针调用函数是未定义的,但将这种指针转换回指向原函数类型的指针将生成指向原函数的指针值。
8) 一些实现上(特别是在任何 POSIX 兼容的系统上,即基于 dlsym 的要求),函数指针可以转换成 void* 或任何其他对象指针,反之亦然。如果实现支持双向的转换,那么转换回原类型将生成原值,否则结果指针不能安全地解引用或调用。
9) 任何指针类型的空指针值都可以转换到任何其他指针类型,并产生该类型的空指针值。注意不能用 reinterpret_cast 将空指针常量 nullptr 或任何其他 std::nullptr_t 类型的值转换成指针:此时应该使用隐式转换或 static_cast
10) 成员函数指针可以转换成指向不同类型的不同成员函数的指针。转换回原类型将生成原值,否则结果指针不能安全使用。
11) 指向某类 T1 的成员对象的指针可以转换成指向另一个类 T2 的另一个成员对象的指针。如果 T2 的对齐不比 T1 更严格,那么转换回原类型 T1 将生成原值,否则不能安全地使用结果指针。

同所有转型表达式,结果是:

  • 左值,如果 新类型 是左值引用类型或到函数类型的右值引用类型 (C++11 起)
  • 亡值,如果 新类型 是到对象类型的右值引用类型;
(C++11 起)
  • 否则是纯右值。

关键词

reinterpret_cast

类型别名化

如果下列条件均不满足,每当试图通过 别名类型 类型的泛左值读取或修改类型为 动态类型 的对象的值时,行为未定义:

  • 别名类型动态类型 相似
  • 别名类型动态类型 的(可有 cv 限定的)有符号或无符号变体。
  • 别名类型std::byte (C++17 起)charunsigned char:这容许将任何对象的对象表示作为一个字节数组加以检验。

非正式地说,忽略顶层 cv 限定性,如果两个类型符合下列条件,那么它们相似

  • 它们是同一类型;或
  • 它们都是指针,且被指向的类型相似;或
  • 它们都是指向相同类的成员指针,且被指向的成员类型相似;或
  • 它们都是大小相同的数组或都是未知边界数组,且数组元素类型相似。
(C++20 前)
  • 它们都是数组,大小相同或至少一个是未知边界数组,且数组元素类型相似。
(C++20 起)

例如:

  • const int * volatile *int * * const 相似;
  • const int (* volatile S::* const)[20]int (* const S::* volatile)[20] 相似;
  • int (* const *)(int *)int (* volatile *)(int *) 相似;
  • int (S::*)() constint (S::*)()  相似;
  • int (*)(int *)int (*)(const int *)  相似;
  • const int (*)(int *)int (*)(int *)  相似;
  • int (*)(int * const)int (*)(int *) 相似(它们是同一类型);
  • std::pair<int, int>std::pair<const int, int>  相似。

此规则允许进行基于类型的别名分析,即编译器假设通过一个类型的泛左值读取的值,不会被通过不同类型的泛左值的写入所修改(依据上述例外情况)。

注意,许多 C++ 编译器作为非标准语言扩展放松此规则,以允许通过联合体的不活跃成员的进行类型错误的访问(这种访问在 C 中并不是未定义的)。

注解

假设符合对齐要求,那么 reinterpret_cast 在处理指针可以互相转换 对象的少数受限情况外,不会更改指针的值

struct S1 { int a; } s1;
struct S2 { int a; private: int b; } s2; // 非标准布局
union U { int a; double b; } u = {0};
int arr[2];
 
int* p1 = reinterpret_cast<int*>(&s1); // p1 的值是“指向 s1.a 的指针”
                                       // 因为 s1.a 与 s1 的指针可以互相转换
 
int* p2 = reinterpret_cast<int*>(&s2); // reinterpret_cast 不会更改 p2 的值,
                                       // 它是“指向 s2 的指针” 
 
int* p3 = reinterpret_cast<int*>(&u);  // p3 的值是“指向 u.a 的指针”:
                                       // u.a 与 u 的指针可以互相转换
 
double* p4 = reinterpret_cast<double*>(p3); // p4 的指针是“指向 u.b 的指针”:
                                            // u.a 与 u.b 的指针可以互相转换,
                                            // 因为它们都和 u 指针可以互相转换
 
int* p5 = reinterpret_cast<int*>(&arr); // reinterpret_cast 不会更改 p5 的值,
                                        // 它是“指向 arr 的指针”

在实际上不代表适当类型的对象的泛左值(例如通过 reinterpret_cast 所获得)上进行代表非静态数据成员或非静态成员函数的成员访问将导致未定义行为:

struct S { int x; };
struct T { int x; int f(); };
struct S1 : S {};    // 标准布局
struct ST : S, T {}; // 非标准布局
 
S s = {};
auto p = reinterpret_cast<T*>(&s); // p 的值是“指向 s 的指针”
auto i = p->x; // 类成员访问表达式是未定义行为:s 不是 T 对象
p->x = 1; // 未定义行为
p->f();   // 未定义行为
 
S1 s1 = {};
auto p1 = reinterpret_cast<S*>(&s1); // p1 的值是“指向 S 的 s1 子对象的指针”
auto i = p1->x; // OK
p1->x = 1;      // OK
 
ST st = {};
auto p2 = reinterpret_cast<S*>(&st); // p2 的值是“指向 st 的指针”
auto i = p2->x; // 未定义行为
p2->x = 1;      // 未定义行为

许多编译器在这种情况下发布“严格别名化”警告,即使在技术上这种构造所违背的并不是称为“严格别名化规则”段落的规则。

严格别名化及其相关规则的目的,是启用基于类型的别名分析,如果程序能合法地创建一种情形,使得两个指向无关类型的指针(例如一个 int* 和一个 float*)能同时存在并可以一起用来加载或存储同一内存(见此 SG12 reflector 上的邮件),那么别名分析会普遍无效。因此任何看起来能够创建这种情形的技巧都必然导致未定义行为。

当需要将对象的字节解释为不同类型的值时,可以使用 std::memcpy std::bit_cast (C++20 起)

double d = 0.1;
std::int64_t n;
static_assert(sizeof n == sizeof d);
// n = *reinterpret_cast<std::int64_t*>(&d); // 未定义行为
std::memcpy(&n, &d, sizeof d);               // OK
n = std::bit_cast<std::int64_t>(d);          // 同样 OK

如果实现提供了 std::intptr_t 和/或 std::uintptr_t,那么从指向对象类型或 cv void 的指针到这些类型的转型始终有良好的定义。然而对于函数指针并没有这种保证。

(C++11 起)

标准中定义严格别名化规则的段落曾经含有两条额外条例,部分从 C 继承而来:

  • 别名类型聚合类型联合体类型,它保有前述各类型之一作为它的元素或非静态成员(递归地包含子聚合体的元素和被包含的联合体的非静态数据成员)。
  • 别名类型动态类型 的(可有 cv 限定的)基类

这些条例所描述的情况不可能在 C++ 出现,从而从上面的讨论中省略。在 C 中,聚合复制和赋值将聚合体对象作为整体访问。但 C++ 中始终通过成员函数调用进行这种操作,这会访问单独的子对象而非整个对象(或在联合体的情况下,复制对象表示,即经由 unsigned char)。这些条例最终经由 CWG 问题 2051 移除。

示例

演示 reinterpret_cast 的一些用法:

#include <cstdint>
#include <cassert>
#include <iostream>
 
int f() { return 42; }
 
int main()
{
    int i = 7;
 
    // 指针到整数并转回
    std::uintptr_t v1 = reinterpret_cast<std::uintptr_t>(&i); // 不能误用 static_cast
    std::cout << "&i 的值是 " << std::showbase << std::hex << v1 << '\n';
    int* p1 = reinterpret_cast<int*>(v1);
    assert(p1 == &i);
 
    // 到另一函数指针并转回
    void(*fp1)() = reinterpret_cast<void(*)()>(f);
    // fp1(); 未定义行为
    int(*fp2)() = reinterpret_cast<int(*)()>(fp1);
    std::cout << std::dec << fp2() << '\n'; // 安全
 
    // 通过指针的类型别名化
    char* p2 = reinterpret_cast<char*>(&i);
    std::cout << (p2[0] == '\x7' ? "本系统是小端的\n"
                                 : "本系统是大端的\n");
 
    // 通过引用的类型别名化
    reinterpret_cast<unsigned int&>(i) = 42;
    std::cout << i << '\n';
 
    [[maybe_unused]] const int &const_iref = i;
    // int &iref = reinterpret_cast<int&>(const_iref); // 编译错误——不能去除 const
    // 必须用 const_cast 代替:int &iref = const_cast<int&>(const_iref);
}

可能的输出:

&i 的值是 0x7fff352c3580
42
本系统是小端的
42

缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

缺陷报告 应用于 出版时的行为 正确行为
CWG 195 C++98 不允许函数指针和对象指针间的转换 改为条件性支持
CWG 658 C++98 未指明(除转换回自身原本类型以外)指针间转换的结果 指明被指向类型满足对齐要求时的转换结果
CWG 799 C++98 不明确 reinterpret_cast 能进行哪种恒等转换 使之明确
CWG 1268 C++11 reinterpret_cast 只能将左值转型到引用类型 亡值也可以

参阅

const_cast 转换 添加或移除 const
static_cast 转换 进行基本转换
dynamic_cast 转换 进行有检查的多态转换
显式转型 在类型间自由转换
标准转换 从一个类型到另一类型的隐式转换