19 · 内存管理 ★C 的第二大关卡
Java 有 GC(垃圾回收);C 没有。你申请的内存,必须你自己还。
内存分区(脑中要有图)
高地址 ┌──────────────┐
│ 栈 Stack │ 局部变量、函数调用 —— 自动分配/回收
├──────────────┤
│ 堆 Heap │ malloc 分配 —— 手动 malloc / free
├──────────────┤
│ 全局/静态区 │ 全局变量、static —— 程序全程存活,自动清零
├──────────────┤
│ 常量区 │ "字符串字面量" —— 只读
低地址 └──────────────┘
四个函数
#include <stdlib.h>
int *p = (int *)malloc(5 * sizeof(int)); // 申请 5 个 int,内容未初始化
int *q = (int *)calloc(5, sizeof(int)); // 申请并**全部清零**
p = (int *)realloc(p, 10 * sizeof(int)); // 扩容到 10 个 int
free(p); // ✅ 用完必须释放
p = NULL; // ✅ 释放后置 NULL,防止悬空指针
三条铁律(背下来)
- malloc 后必须判空(内存可能申请失败)
c if (p == NULL) { /* 处理内存不足 */ } - 谁分配谁释放,malloc 与 free 一一配对(否则内存泄漏)
- free 之后立刻把指针置 NULL(否则悬空指针,再解引用就是未定义行为)
典型错误
| 错误 | 后果 |
|---|---|
| 忘记 free | 内存泄漏,长时间运行的程序越吃越多 |
| free 两次 | double free,崩溃 |
| free 后继续用 | 悬空指针,未定义行为 |
| 访问越界 | 破坏相邻内存,神秘崩溃 |
| 返回局部数组的地址 | 栈已销毁,读到垃圾 |
新手必备工具
- 每个
malloc旁边写注释提醒自己free。 - 大项目用 Valgrind(Linux)/ ASan(
gcc -fsanitize=address)检测泄漏。
⚡ Java 对比
// C:完全手动
int *arr = malloc(n * sizeof(int));
/* ... 用完 ... */
free(arr);
// Java:交给 GC
int[] arr = new int[n];
// 不用管,GC 会回收
学 C 最值钱的收获就是:你终于知道 Java 里那些「不用管的东西」背后发生了什么。
关联
09-指针 · 08-数组 · 12-结构体 · 20-未定义行为与安全函数