0113、安全开发篇(二):内存管理与指针限定
返回:C/C++ 进阶提升目录 · 安全开发总目录
这一章围绕“指针到底能不能安全地访问内存”展开。C 语言给了很大的自由,也把边界检查的责任交给了程序员。后面做安全开发时,很多 bug 都会落在空指针、野指针、越界、重复释放和类型解释错误上。
3.0 内存属性探测
内存不仅有地址,还有属性:能不能读、能不能写、能不能执行、属于栈还是堆、是否映射文件。安全分析时看到一个地址,第一反应应该是问:这个地址在哪个区域?权限是什么?为什么会被访问?
| 属性 | 复盘问题 |
|---|---|
| 可读 | 读这个地址是否会异常 |
| 可写 | 是否允许修改内容 |
| 可执行 | 是否可能被当成代码执行 |
| 私有/映射 | 来自堆、栈、模块还是映射文件 |
3.1 void * 指针
void * 是“无类型指针”,可以保存任意对象地址,但不能直接解引用。它常用于通用接口,比如内存拷贝、回调参数、容器数据。
1 |
|
复盘重点:void * 必须配合明确的真实类型,否则同一段内存会被错误解释。
3.2 堆区内存管理
堆内存由程序主动申请、主动释放。它比栈更灵活,但也更容易出错:忘记释放、重复释放、释放后继续使用,都是典型问题。
1 |
|
| 操作 | 复盘重点 |
|---|---|
malloc |
判断返回值是否为空 |
| 使用 | 永远带着长度一起使用 |
free |
释放后不要继续访问 |
| 置空 | 降低悬空指针误用概率 |
3.3 内存回收机制
C 语言没有自动垃圾回收。谁申请,谁负责释放;如果所有权不清楚,代码很快会变乱。
| 问题 | 表现 | 解决习惯 |
|---|---|---|
| 内存泄漏 | 程序运行久了占用越来越多 | 明确释放位置 |
| 重复释放 | 崩溃或堆结构损坏 | 释放后置空,统一所有权 |
| 释放后使用 | 偶发崩溃或数据异常 | 不保留过期指针 |
| 越界写 | 邻近内存被破坏 | 所有函数都传长度 |
3.4 设置内存数据
memset 常用于初始化内存,但它按字节设置,不理解对象类型。对结构体和字节缓冲区常用;对复杂 C++ 对象不要乱用。
1 |
|
3.5 常量指针与指针常量
const 指针组合是 C/C++ 的基本功。核心看 const 修饰谁:修饰指向内容,还是修饰指针本身。
| 写法 | 含义 |
|---|---|
const int *p |
不能通过 p 修改指向内容,p 可以改指向 |
int * const p |
p 不能改指向,可以通过 p 修改内容 |
const int * const p |
指向和内容都不能通过 p 改 |
1 | void read_only(const char *text) { |
3.6 指向常量的指针与常量的指针常量
这节可以当作上节的加强复盘。函数参数里优先用 const 表达只读意图,既能保护数据,也能让调用者看懂接口。
| 场景 | 推荐写法 |
|---|---|
| 只读字符串 | const char *text |
| 只读数组 | const int *arr, int count |
| 不允许改指向的内部指针 | T * const p |
| 既不改指向也不改内容 | const T * const p |
3.7 数组寻址与指针步长
指针加一不是地址加一字节,而是加一个元素大小。int * 加一通常跨过 4 字节,char * 加一跨过 1 字节。
1 |
|
3.8 指针运算规则
指针运算必须在同一个数组或连续对象范围内才有意义。跨对象乱算地址,在 C 语言里非常危险。
| 运算 | 合法场景 |
|---|---|
p + n |
数组范围内移动 |
p - n |
数组范围内回退 |
p2 - p1 |
两个指针指向同一数组 |
| 比较 | 同一数组范围内比较更有意义 |
3.9 多级指针属性探测
多级指针本质是“指针的地址”。常见于需要在函数里修改外部指针的场景,比如申请内存后把地址返回给调用者。
1 |
|
复盘时把每一级画出来:out 是二级指针,*out 是外部指针变量,**out 才是最终的整数。
本章最小复盘代码
1 |
|
学习检查清单
| 检查项 | 状态 |
|---|---|
能说明 void * 为什么需要显式类型解释 |
待复盘 |
| 能写出申请、使用、释放、置空的堆内存流程 | 待复盘 |
能区分 const int * 和 int * const |
待复盘 |
| 能解释指针步长和数组寻址公式 | 待复盘 |
| 能画出二级指针修改外部指针的过程 | 待复盘 |






