1.5 指针寻址的内存真相


1.5 指针寻址的内存真相

本节摘要:指针就是一个普通变量,只是它的值被解释为另一个对象的地址。解引用、取地址、指针算术、多级指针,全部围绕"值是地址"这一个事实展开。本节训练拿地址画图的基本功,把悬垂指针、野指针、空指针三大凶案现场逐一复原,为第 3 章智能指针的所有权模型打好地基。

学习目标

阅读完本节,你应当能够:

  1. 画出任意指针操作序列的指向关系图;
  2. 解释指针算术为什么按指向类型的大小跳步;
  3. 区分野指针、悬垂指针、空指针并写出各自的防御手段;
  4. 说明 const 修饰指针时的三种组合含义。

一、指针变量自己也是对象

先破除神秘感:

int x = 42; int* p = &x; // p 是一个变量 值为 x 的地址

p 自己也要占内存——64 位平台上 8 字节,和 int 变量一样老老实实住在栈帧里。它唯一的特别之处是"值被当作地址解释"。&x 取出 x 的地址存进 p,*p 顺着地址找到 x。用一张地址表把状态写清楚:

图:一次指针赋值前后的内存状态

图:一次指针赋值前后的内存状态

二级指针与指针的引用(int*&)服务的场景相同:函数想改"调用者的指针指向谁"(比如分配缓冲后回传地址)。日常代码里 int*& 更直观,C 风格接口里 int** 更常见。

二、指针算术:按指向类型跳步

int arr[4] = {10, 20, 30, 40}; int* p = arr; p + 1; // 地址增加 4 即 sizeof(int) *(p + 2); // 30 即 arr[2]

p + 1 不是地址加 1 字节,而是加"一个元素的宽度"——这是数组下标与指针算术能互换的根基:arr[i] 在机器层面就是 *(arr + i)。也正因如此,指针算术只在数组(或同一分配块)内部有意义,跨对象比较与加减是未定义行为。void* 不能做算术(不知道步长),char* 步长为 1,常用于逐字节查看内存。

三、三大凶案现场

野指针(未初始化)

int* p; // 栈上的值是随机图案 *p = 1; // 往一个随机地址写入 未定义行为

防御很简单:声明即初始化,暂时没有目标就给 nullptr

悬垂指针(目标已死)

int* p = new int{42}; delete p; // 堆内存归还 但 p 的值没变 *p = 7; // 悬垂:写一块已归还的堆内存 未定义行为 delete p; // 双重释放 同样未定义 堆管理器状态被破坏

delete 只做两件事:调用析构、归还内存;它不会顺手把指针置空。好习惯是 delete 后立刻 p = nullptr;——对空指针 delete 是安全的无操作。但真正的解法在第 3 章:让栈上的智能指针对象在离开作用域时自动释放堆内存,悬垂问题从源头消失。

空指针nullptr(C++11)取代 NULL,它有专属类型不会与整数混淆。解引用空指针几乎必然立刻段错误——这其实是三大凶案里最"仁慈"的一种,因为崩溃现场离案发地最近。

int* p = nullptr; if (p) { /* 非空才使用 */ } // C++17 提供了更直的表达 if (p != nullptr) { }

四、const 与指针的三种组合

const 修饰指针时,看它站在星号前还是星号后:

int a = 1, b = 2; const int* p1 = &a; // 指向常量的指针:不能通过 p1 改 a 但 p1 可改指 int* const p2 = &a; // 常量指针:p2 只能指向 a 但可通过 p2 改 a const int* const p3 = &a; // 双重锁定:既不能改指也不能改值 p1 = &b; // 可以 // *p1 = 10; // 编译错误 // p2 = &b; // 编译错误 *p2 = 10; // 可以 a 变为 10

读法窍门:从右往左读,const 修饰谁谁不可变。工程含义更重要——接口用 const int*const int& 是一份契约:承诺只读不改。标准库里 const char* 返回值、const& 传参,全是这个契约的应用。函数后置的 constint get() const;)承诺不改对象自身,同一逻辑在成员函数层面的延伸,第 2 章细讲。

顺带梳理几组易混操作,全部能用"值是地址"一句话复原:*p++*(p++),先取当前值再后移;(*p)++ 是把所指值加一;&arr[0]arr 同值但类型分别是 int*int[4]。拿不准时加括号,别赌优先级。

💡 关键直觉:指针给了你直接搬弄内存的权限,但语言不再替你保管任何事。C++ 的现代答案不是没收权限,而是给权限上锁——引用消灭"可空"、span 消灭"丢长度"、智能指针消灭"忘释放",它们都是包着安全外衣的指针。理解裸指针,是为了真正看懂这些外衣。

本节要点回顾

  • 指针是值被解释为地址的普通变量:自身占 8 字节,二级指针即"指向指针的指针"。
  • 算术按类型步长arr[i]*(arr + i),跨界运算是未定义行为。
  • 三案速查:野指针靠初始化、悬垂靠置空或智能指针、空指针靠判空——崩溃最及时的反而最好查。
  • const 三组合:读法从右往左;接口层的 const 是只读契约。
  • 指针是底座:引用、span、智能指针都是受限指针,学它们之前先看透本体。

第一章到此收官。你已经能画出程序的内存分区、推算类型宽度、回放栈帧轮回、追踪指针指向。下一章把镜头转向"复合对象":类在内存里长什么样,继承如何拼接,多态怎样经由虚表完成一次间接跳转。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U