C 语言内存错误
更新时间:2026-08-26。本文是
languages/c/主题入门层第 55 篇。如果说泄漏是"借了不还",那越界、双重释放、悬垂指针就是"暴力破坏账本"——它们不报错、不崩溃、不按套路出牌,可能几小时后才在毫不相干的地方炸开。C 的内存错误之所以难查,就难在错误发生点和爆炸点往往相隔十万八千里。
本文要回答的问题
- 三类内存错误分别是什么?表现为何如此诡异?
- 为什么错误可能"延时发作"?
- 怎么快速定位?
一、缓冲区越界(buffer overflow)
c
int arr[5];
for (int i = 0; i <= 5; i++) // 越界:i=5 时 arr[5] 已出界
arr[i] = i * 10;写越界不保证立刻崩溃——可能悄悄踩坏相邻变量、堆管理元数据,甚至函数返回地址:
| 越界目标 | 后果 |
|---|---|
| 相邻局部变量 | 数据莫名被改 |
| 栈上返回地址 | 返回时跳到乱地址 → 崩溃或攻击 |
| 堆块元数据 | 后续 malloc/free 崩溃 |
| 无主内存 | 暂时没事,说不准哪天出事 |
arr[5] 这种"差一错误"(off-by-one)尤其阴险:循环 <= 写成了 <=。
二、双重释放(double free)
c
int *p = malloc(10 * sizeof(int));
free(p);
free(p); // double freeglibc 较新版本会检测并 abort:
free(): double free detected in tcache 2但如果中间夹了别的 malloc/free,堆链表可能被破坏,错误在别处爆发:
c
free(p);
int *q = malloc(20 * sizeof(int)); // 可能复用 p 的块
free(p); // 再释放一次 → 堆损坏
// 后面任何 malloc/free 都可能崩三、悬垂指针(use-after-free)
c
char *p = malloc(100);
strcpy(p, "hello");
free(p);
printf("%s\n", p); // use-after-free:读已释放内存已释放的内存内容还在(只是标记可复用),所以:
- 可能"碰巧"还是旧值 → 看起来正常;
- 可能被下一次 malloc 复用 → 读到别人写的新数据;
- 可能在 free 时被 glibc 写入了管理字段 → 读到垃圾。
"碰巧正常"是最可怕的——线上偶发、本地复现不了。
四、为什么内存错误会"延时发作"
堆/栈的损坏往往是"写坏了别人家的地",但房东(分配器)只在自己收租时检查:
写坏 p 后面的块头 → 当下无感
下一个 malloc 用这块 → 分配器读块头 → 数值异常 → 崩溃所以调试铁律:崩溃点 ≠ 错误点,要看核心转储/工具的调用栈,别在崩溃处找原因。
五、用 ASan 一网打尽
AddressSanitizer 在每次读写前后插入检查,越界、双释、悬垂在发生那一刻就报:
bash
gcc -g -fsanitize=address -o prog bug.c
./prog典型输出直接给出错误行:
==12345==ERROR: AddressSanitizer: heap-use-after-free on address ...
#0 ... at bug.c:12 ← 出错位置
#1 ... in main bug.c:15检测覆盖:堆越界、栈越界、全局越界、use-after-free、double free、泄漏。完整教程见ASan 内存检测。
六、预防习惯
- 循环边界写清楚:
i < n而非i <= n; - free 后置 NULL:悬垂变确定性空指针崩溃,双释变 no-op;
- 数组/缓冲区越界多用 sizeof/哨兵:如
snprintf而非sprintf; - 收尾跑 ASan/Valgrind:让工具背锅,别让用户背锅。
七、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| off-by-one | 偶发崩溃 | 边界 i < n |
| 在崩溃点找原因 | 找不到根因 | 看栈回溯找错误点 |
| 双释夹了 malloc | 堆损坏连环崩 | 置 NULL 后再 free |
| 本地正常线上炸 | 未初始化/越界 | ASan 必跑 |
| 用 sprintf | 长输入越界 | 用 snprintf |
八、与本站主线衔接
- 泄漏与工具全集,见内存泄漏入门;
- ASan/Valgrind 完整用法,见ASan 内存检测、Valgrind 内存检测;
- 栈溢出与保护页机制,见段错误与 core dump;
- 越界被攻击利用的话题,见缓冲区溢出与安全。
一句话总结
越界(写超了)、双释(释放两次)、悬垂(释放后用)三类内存错误共同点是"延时发作"——崩溃点 ≠ 错误点;对策是边界写对、free 后置 NULL、ASan 在发生时刻精准定位——内存错误是 C 的"地雷",工具能帮你扫雷,别裸奔。