C 内存管理(Memory Management)
手动内存管理是 C 语言的核心难点之一。本章介绍栈与堆、malloc/calloc/realloc/free 的用法、所有权约定、常见错误与实战建议。
1. 栈与堆
- 栈(stack):函数局部变量、参数等,自动分配与释放,容量有限。
- 堆(heap):通过分配函数动态申请,需要手动释放,灵活但易出错。
2. 分配与释放 API
c
#include <stdlib.h>
void *p = malloc(100); // 未初始化的 100 字节
void *q = calloc(25, 4); // 置零的 25×4 字节
p = realloc(p, 200); // 调整大小,可能搬迁
free(q); q = NULL; // 释放并置空指针
free(p); p = NULL;要点:
- 检查返回值是否为 NULL。
- realloc 失败时返回 NULL,原指针仍然有效且未改变(建议用临时指针接收)。
c
void *tmp = realloc(p, new_size);
if (!tmp) { /* 处理失败,p 仍然可用 */ }
else { p = tmp; }3. 所有权与生命周期
- 谁分配,谁释放;或者明确转移所有权。
- 一个指针只能被 free 一次;重复释放是未定义行为。
- 释放后指针悬空,建议立即置为 NULL。
4. 常见错误
- 内存泄漏:忘记释放或失去引用。
- 悬空指针:free 后继续使用。
- 越界读写:写出缓冲区范围导致崩溃或安全问题。
- 使用未初始化内存:malloc 得到的是未定义内容,应先初始化或使用 calloc。
5. 分配大小与溢出
- 使用
sizeof *ptr习惯减少类型修改时的错误:
c
int *a = malloc(n * sizeof *a);- 检查乘法是否溢出(n × size):
c
if (n > SIZE_MAX / sizeof *a) { /* 防止溢出 */ }6. 动态数组与增长策略
- 典型扩容:容量不足时扩展为 1.5× 或 2×,减少 realloc 次数。
c
size_t cap = 0, len = 0; int *arr = NULL;
void push(int v){
if (len == cap) {
size_t ncap = cap ? cap*2 : 8;
int *tmp = realloc(arr, ncap * sizeof *arr);
if (!tmp) { /* 处理失败 */ return; }
arr = tmp; cap = ncap;
}
arr[len++] = v;
}7. 与结构体配合:灵活数组成员
c
typedef struct {
size_t len;
int data[]; // 结尾的灵活数组(C99)
} IntVec;
IntVec *v = malloc(sizeof *v + n * sizeof v->data[0]);8. 拷贝与移动
- 原始内存拷贝:
memcpy,memmove(重叠时用 memmove)。 - 初始化:
memset,但不要用于非平凡对象的语义初始化(C 里大多是 POD,可用)。
9. 调试建议
- 统一创建/销毁接口,集中管理资源。
- 使用工具检测泄漏与越界(如平台下的内存调试器)。
- 开启编译器警告,进行边界与返回值检查。
10. 小结
遵循所有权与生命周期原则,规范使用分配 API,做好容量与边界检查,是写好 C 内存管理的关键。