7
内存管理:C 程序员的成人礼
Memory Layout · malloc / free
这一章是 C 和 Python 的分水岭。Python 帮你自动回收内存,C 不——你自己申请的堆内存,必须自己还回去。忘了还,就是"内存泄漏",程序越跑越胖直到被系统干掉。先看一张内存地图。
| 区域 | 放什么 |
|---|---|
| 代码段 text | 你的程序指令本身,只读。 |
| 数据段 data | 已初始化的全局变量、静态变量。 |
| BSS 段 | 未初始化的全局/静态变量,程序启动时清零。 |
| 堆 heap | malloc 申请的地盘,向上增长,手动管理。最大、最自由、最容易出事。 |
| 栈 stack | 函数局部变量、函数调用现场,向下增长,自动分配自动释放。 |
栈 vs 堆:自动 vs 手动
比什么时候用栈,什么时候用堆
栈:函数里的局部数组、变量,函数一结束自动回收,免费但快,空间小(通常几 MB)。数组太大(比如 1000 万个数)放栈上直接崩。
堆:用 malloc / new 申请,空间大,但你得记得 free。生命期不受函数限制——函数返回了它还在。
malloc / calloc / realloc / free
| 函数 | 作用 |
|---|---|
malloc(n字节) | 在堆上要一块 n 字节的内存,返回首地址。内容不初始化,是垃圾值。 |
calloc(个数, 单字节) | 和 malloc 一样,但会把内存清零。 |
realloc(p, 新大小) | 把 p 指向的旧块改成新大小(可能搬家)。动态扩容数组靠它。 |
free(p) | 把 p 这块内存还给系统。申请和释放必须配对。 |
经典四步:申请 → 使用 → 检查 → 释放
#include <stdio.h>
#include <stdlib.h> // malloc/free 在这里
int main() {
// 申请:要 5 个 int 的空间。sizeof(int) 自动算字节数
int *arr = (int *)malloc(5 * sizeof(int));
if (arr == NULL) { // 一定要检查申请成功没
printf("内存不足\n");
return 1;
}
// 像普通数组一样用
for (int i = 0; i < 5; i++) arr[i] = i * 10;
printf("%d\n", arr[3]); // 30
free(arr); // 用完必须还!
arr = NULL; // 好习惯:free 后置空,杜绝悬空指针
return 0;
}
C 程序员的四种死法
① 内存泄漏:malloc 了不 free,程序跑一次漏一点,长跑必出事。
② 重复释放:同一块内存 free 两次,直接崩。
③ 使用已释放内存:free 完还在用那个指针(悬空)。
④ 缓冲区溢出:往 5 格的数组里写了第 6 个元素,踩坏旁边的数据——历史上大量安全漏洞都源于此。
解药:用 valgrind ./程序 跑一遍,它会清清楚楚告诉你哪行 malloc 了没 free。