有时候,程序要用的空间大小,得等跑起来才知道——比如用户临时输入要存多少个数字。这时候,就要向内存「借」一块刚刚好的地,用完再还回去。这就是动态内存管理。
三个"借地"函数
malloc:借一块不打扫的房间
void* malloc(size_t size); // 申请,但不初始化
int* p = (int*)malloc(n * sizeof(int));
if (p == NULL) { perror("malloc"); return 1; } // 一定要检查返回值
calloc:借一块还帮你打扫的房间
void* calloc(size_t num, size_t size); // 申请并初始化为 0
// calloc == malloc + 初始化
realloc:房间不够大,帮你搬家扩容
void* realloc(void* p, size_t size);
// 情况1:原地后面的空间够 → 直接返回新地址
// 情况2:不够 → 把数据拷贝到新地方,返回新地址,释放旧地址
int* p2 = realloc(p1, 10 * sizeof(int));
if (p2 == NULL) { ... } // 失败时 p1 还活着,别急着丢
p1 = p2;
free 的温柔约定
借了地,用完一定要还——这是和内存之间最重要的约定:
free只能释放malloc / calloc / realloc申请的空间,而且必须传回它最初的起始地址。释放后要把指针手动置成NULL,才不会变成危险的「野指针」。
六个经典的翻车现场
- ① 对
NULL指针解引用 - ② 对动态开辟空间越界访问
- ③ 对非动态内存用
free - ④ 用
free释放动态空间的一部分 - ⑤ 同一块内存释放多次
- ⑥ 忘了释放 → 内存泄漏
一个踩坑无数的笔试题
void GetMemory(char* p) {
p = (char*)malloc(100); // 只改了形参,实参 str 还是 NULL!
}
void Test(void) {
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world"); // 对空指针拷贝 → 崩
}
正确姿势有两个:用二级指针传地址,或直接用返回值把地址带回来。
柔性数组:结构体的小尾巴
C99 允许结构体的最后一个成员是一个大小未知的数组,它就像一条可以随时伸缩的小尾巴:
struct S {
int n;
char c;
char arr[]; // 柔性数组成员
};
它不占 sizeof,只能通过动态内存分配来使用。好处有两个:一是一次性分配、释放也只需一个 free,二是连续内存访问更快,还减少内存碎片。
✿ 摘要
- 动态内存住在堆区,由 malloc / calloc / realloc / free 管理。
malloc不初始化,calloc初始化为 0,realloc扩容搬家。- 使用后必须
free,并置NULL;free 只认 malloc 家族、只认起始地址。 - 常见错误:空指针解引用、越界、重复释放、内存泄漏等六种。
- 柔性数组是结构体尾部的大小可变数组,方便整体分配与释放。
愿当下的花如你所见般美丽
评论交流
欢迎留下你的想法