这一章在干嘛?

数组长度必须在编译期写死,运行时才知道要多少空间怎么办?答案是堆。本章讲 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 家族的原样返回值

所有权思维:

每块堆内存问一句「谁负责释放?」——同一函数内配对最简单;跨函数传递时在接口注释里写明归属(调用方释放还是被调方释放)。职责不清是泄漏的总根源。