C++ reinterpret_cast

在 C++ 提供的 4 种专用类型转换运算符中,reinterpret_cast(重新解释转换)是最偏向底层的一个。从英文拼写上就很容易记住:reinterpret 由 “re(重新)” 和 “interpret(解释)” 组合而成,意思是对同一块内存重新解释。

它的工作方式跟前面学过的 static_cast 完全不同:static_cast<double>(65) 会按照数值规则算出一个新的浮点数 65.0;而 reinterpret_cast 不会改变内存里原有的二进制数据,只是换一种指针或引用类型去重新看待这块内存。

在实际开发中,普通的业务代码很少会直接用到它,小伙伴们不需要有心理负担。它主要出现在二进制文件读写、网络通信协议或底层硬件接口等场景中。

基本语法与常见用法

当我们需要按单字节读取某个变量的底层内存,或者把指针保存的内存地址转换成整数时,可以使用 reinterpret_cast 来完成转换。

语法:

reinterpret_cast<目标类型>(表达式)

说明:

其中,尖括号 <> 里的目标类型与圆括号 () 里的表达式,主要用于以下 3 类底层转换:① 指针与指针之间;② 引用与引用之间;③ 指针与足够大的整数类型之间。下面我们来看看最常见的两种用法。

1. 转为 char* 按字节读取内存

读写二进制文件或收发网络数据时,底层接口常常要求我们把普通变量的地址当成 “字节序列” 传进去。这时就可以通过 reinterpret_cast 将普通指针转换为 char* 或 unsigned char*。

示例 1:转为 char* 字节指针

#include <iostream>

int main() {
    int num = 65;

    // 将 int* 重新解释为 char* 字节指针
    char* bytePtr = reinterpret_cast<char*>(&num);

    std::cout << num << '\n';
    std::cout << *bytePtr << '\n';
    return 0;
}

运行结果如下。

65
A

分析:

reinterpret_cast<char*>(&num) 表示把整型变量 num 的地址当成 char* 字节指针来使用。在日常使用的主流电脑中,多字节整数采用低位字节排在前面的存储顺序,因此正整数 65 刚好保存在起始的第 1 个字节里,后面 3 个字节均为 0。

在转换过程中,内存里的二进制数据没有任何改变,只是指针 bytePtr 把起始的第 1 个字节当成了 char 类型来读取。由于 65 在 ASCII 码表中对应的正好是大写字母 'A',因此 *bytePtr 输出了字符 'A'。

这里大家要区分清楚:如果平时只是想把整数 65 转为字符 'A',直接使用 static_cast<char>(65) 就可以了。这里转为 char* 只是为了演示按单字节读取底层内存,这也是 C++ 标准中明确允许的合法操作。

2. 指针与整数地址互转

对接操作系统或底层硬件接口时,有时还需要我们把指针保存的内存地址转换成整数来传递。C++ 在 <cstdint> 头文件中提供了 std::uintptr_t 类型(由 unsigned、int、ptr 缩写而成),专门用来保存指针的整数地址。

示例 2:指针与整数地址互转

#include <cstdint>    // 提供 std::uintptr_t
#include <iostream>

int main() {
    int num = 100;
    int* ptr = &num;

    // 将指针转换为整数地址
    std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(ptr);
    std::cout << "整数地址:" << addr << '\n';

    // 将整数地址还原为 int* 指针
    int* restoredPtr = reinterpret_cast<int*>(addr);
    std::cout << "还原后的值:" << *restoredPtr << '\n';
    return 0;
}

运行结果如下。

整数地址:140732898695068
还原后的值:100

分析:

在这个例子中,我们先使用 reinterpret_cast<std::uintptr_t>(ptr) 把指针 ptr 保存的地址转成无符号整数 addr。平时直接打印指针看到的是十六进制地址,这里输出的则是它对应的十进制数值,每次运行程序时数值都会随内存位置变化。

随后,我们又通过 reinterpret_cast<int*>(addr) 将该整数还原回 int* 指针。整个过程只是让同一个地址在 “指针形式” 和 “整数形式” 之间往返了一趟,并没有改动变量 num,因此解引用 *restoredPtr 依然输出 100。

可能有些小伙伴会问:“既然都是转成整数,为什么不直接转成普通的 int 类型呢?” 这是因为在如今的 64 位系统中,一个指针占 8 个字节,而普通的 int 通常只占 4 个字节,根本装不下完整的地址。

如果我们强行写成 reinterpret_cast<int>(ptr),不仅会截断地址,编译器还会直接报错。而 std::uintptr_t 的宽度会自动跟随当前系统的指针大小变化,能够完整装下整个内存地址。

reinterpret_cast 的使用限制

reinterpret_cast 的语法虽然简单,但它是 C++ 中最底层、也最容易误用的转换工具。在使用时,我们需要清楚以下 3 点限制:

  • 不能用于普通数值之间的转换:reinterpret_cast 只处理指针、引用或整数地址表示,不能代替 static_cast 把 double 转换为 int。
  • 不能去掉指针或引用的 const 限定:即使是重新解释底层指针类型,reinterpret_cast 也不能去除 const 只读属性。如果我们需要去掉只读限定,必须使用专门的 const_cast。
  • 无关类型指针强转后不能随意解引用:把 int* 强转成 double* 虽然能通过编译,但两者占用的字节数与存储格式完全不同。一旦我们直接解引用该 double* 指针,就会导致内存越界访问甚至程序崩溃。

在下面这几种错误写法中,前两种会在编译阶段直接报错,最后一种虽然能编译通过,但在运行时解引用是十分危险的操作。

double price = 9.8;
const int limit = 100;
int num = 10;

int a = reinterpret_cast<int>(price);         // 报错:非指针转换
char* p = reinterpret_cast<char*>(&limit);    // 报错:不能去除 const
double* dp = reinterpret_cast<double*>(&num);
std::cout << *dp << '\n';                     // 危险:越界读取内存

4 种类型转换运算符对比

学到这里,C++ 提供的 4 种专用类型转换运算符我们就全部认识了。它们各自有着明确的职责边界,具体区别如下表所示。

4 种类型转换运算符
转换运算符 主要用途 常见场景
static_cast 编译期已知安全的关联类型转换 基础数值互转、enum class 转整数、还原 void*
dynamic_cast 多态继承体系的运行时安全转换 基类指针或引用安全向下转换为派生类
const_cast 调整指针或引用上的 const 限定 原变量非 const 时恢复修改权、对接只读旧接口
reinterpret_cast 底层内存与地址的重新解释 转为 char* 按字节读写、指针与 uintptr_t 互转

在实际开发中,90% 以上的显式转换使用的都是 static_cast。只有涉及多态运行时向下检查时,我们才选用 dynamic_cast。涉及去除只读限定或底层地址重解释时,再按上表分工选用 const_cast 或 reinterpret_cast。

提示 遇到类型不匹配的编译报错时,小伙伴们千万不要 “挨个换一种 cast 试试,直到编译通过为止”。先看清两边是什么类型,再按各自的职责选对转换工具,才能避开底层的内存隐患。

上一篇:

下一篇:

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号