📖 第 11 章:动态内存分配(Dynamic Memory Allocation)

🔤 Kenneth Reek《Pointers on C》(C和指针)· Chapter 11
🗨️ 本章金句:「栈上的变量大小和寿命都是编译期定死的;堆的规则只有一条——谁申请,谁释放。」
这一章在干嘛? 数组长度必须在编译期写死,运行时才知道要多少空间怎么办?答案是堆。本章讲 malloc 家族的使用规则和五大经典内存错误——每一项都值得用一生去避免。
11.1 malloc 与 free:基本契约11.2 realloc 与使用要点11.3 五大经典内存错误

11.1 malloc 与 free:基本契约

#include <stdlib.h>

int *arr = malloc(n * sizeof *arr);   /* 按元素个数算,不写死类型名 */
if (arr == NULL) {                    /* 分配可能失败! */
    /* 处理失败:报错/降级/退出 */
}
/* ... 使用 arr[0] ~ arr[n-1] ... */
free(arr);        /* 归还系统;arr 之后不可再使用 */
arr = NULL;       /* 防悬空:顺手置空 */
函数参数 / 行为
malloc(n)分配 n 字节,内容不初始化(是垃圾值)
calloc(k, size)分配 k×size 字节并清零
realloc(p, n)把 p 指向的块调整为 n 字节:可能原地扩/缩,可能搬新址(内容保留),p 为 NULL 时等价 malloc
free(p)释放;p 必须是 malloc/calloc/realloc 的原样返回值
malloc 返回 void*: 赋给其他类型指针时 C 自动转换,不必强转(强转反而可能掩盖「忘了 stdlib.h」的错误——旧式声明把返回值当 int 截断)。写 sizeof *arr 而不是 sizeof(int),改类型时不用改这行。

11.2 realloc 与使用要点

/* 扩容惯用法:用临时变量接,失败时原指针原块都完好 */
int *tmp = realloc(arr, new_n * sizeof *arr);   /* 可能原地扩,也可能搬新址 */
if (tmp == NULL) {
    /* 失败:原块 arr 原样保留、未释放,可继续用或走错误处理 */
} else {
    arr = tmp;                                  /* 成功才更新指针 */
}

使用动态内存的三条纪律:① 分配即检查(NULL 判断);② 初始化别偷懒(malloc 内容是垃圾,需要零值用 calloc 或 memset);③ 释放后不回头(不读、不写、不再 free,指针要么置 NULL 要么出作用域)。

11.3 五大经典内存错误

错误长相后果 / 对策
内存泄漏申请了从不 free;指针被覆盖再也找不到块长期运行程序内存暴涨。对策:每个 malloc 配对 review,出函数前检查所有出口
悬空指针free 后继续用 p;返回栈上局部变量地址读垃圾/写坏别人内存。对策:free 后置 NULL;永不返回局部变量地址
越界读写arr[n](合法到 n-1);malloc(4) 写 5 字节踩坏堆管理结构,崩溃点远离案发点。对策:边界常挂心,工具(ASan/valgrind)常跑
重复释放同一块 free 两次堆结构损坏。对策:free 后置 NULL(free(NULL) 本身无害)
释放非堆指针free 栈变量/静态区/偏移过的地址未定义行为。对策:free 只接 malloc 家族的原样返回值
所有权思维: 每块堆内存问一句「谁负责释放?」——同一函数内配对最简单;跨函数传递时在接口注释里写明归属(调用方释放还是被调方释放)。职责不清是泄漏的总根源。
🧠 小测验
1. malloc 和 calloc 的两个差别?
① 参数形式:malloc 按总字节数,calloc 按元素个数×单个大小(自动防 int 溢出);② 初始化:malloc 内容为垃圾值,calloc 全部清零。需要零初始化选 calloc,性能敏感且随后必然整体写入选 malloc。
2. 为什么 realloc 要用临时变量接返回值?
realloc 可能搬移到新地址,原指针作废;若直接 arr = realloc(arr, n),一旦返回 NULL(失败),原指针被覆盖、原内存无法释放——泄漏。用 tmp 接住,成功才更新 arr,失败时原数据与原指针都完好。
3. 悬空指针怎么产生、怎么防?
free 之后指针值不变但目标已归还系统,继续解引用就是悬空访问;返回局部变量地址同理。防:free 后立即置 NULL;绝不返回栈上变量地址;接口注释写明内存所有权。
← 上一篇🏠 顶层目录下一篇 →