数组的长度在编译时就定死了,但程序运行时往往不知道要存多少数据。这时候就需要动态内存分配:运行时按需向系统申请内存,用完再还回去。malloc 与 free 就是这套流程的"借"与"还",管理不好就会内存泄漏或崩溃。
1. 栈与堆
程序的内存大致分两块:局部变量住在栈上,函数返回时自动释放;动态分配的内存住在堆上,必须手动释放。栈像临时寄存柜,堆像仓库——仓库借了不还,系统内存就会越来越少。
2. malloc:申请内存
malloc(字节数) 在堆上申请一块内存,返回首地址(类型为 void *)。失败时返回 NULL,所以必须检查:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *arr = (int *)malloc(n * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
for (int i = 0; i < n; i++) {
arr[i] = i * i;
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr); // 用完归还
return 0;
}
推荐用 n * sizeof(int) 而不是写死 20,这样在别的平台上也不会出错。
3. calloc 与 realloc
calloc 申请内存并全部清零;realloc 调整已分配内存的大小(可能搬家到新地址):
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *arr = (int *)calloc(4, sizeof(int)); // 4 个元素,全为 0
arr[0] = 10;
arr[1] = 20;
arr = (int *)realloc(arr, 8 * sizeof(int)); // 扩容到 8 个
arr[7] = 99;
for (int i = 0; i < 8; i++) {
printf("%d ", arr[i]); // 10 20 0 0 0 0 0 99
}
printf("\n");
free(arr);
return 0;
}
realloc 失败会返回 NULL 且原内存不动,所以别直接写 p = realloc(p, ...),否则会丢掉原指针。
4. free 与内存泄漏
每个 malloc 都要配对 free。忘记 free 就叫内存泄漏:程序跑得越久,占的内存越多,最终系统资源耗尽。在长驻服务里尤其致命。
#include <stdlib.h>
void leak_example(void) {
int *p = (int *)malloc(100 * sizeof(int));
// 忘了 free(p) —— 每次调用泄漏 400 字节
p = NULL; // 指针丢了,内存再也找不回来
}
Linux 下可用 valgrind 检测泄漏:valgrind ./program。
5. 悬空指针与双重释放
free 之后指针还保存着旧地址,这叫悬空指针;对同一块内存 free 两次叫双重释放,都是未定义行为,可能崩溃。正确姿势:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = (int *)malloc(sizeof(int));
*p = 42;
printf("%d\n", *p);
free(p);
p = NULL; // 关键:释放后立即置空
// free(p); // 即使再 free 一次,对 NULL 是安全的
return 0;
}
6. 实战:动态增长的数组
把"申请-检查-扩容-释放"串起来,写一个简单但完整的动态数组:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int cap = 2, size = 0;
int *data = (int *)malloc(cap * sizeof(int));
for (int i = 1; i <= 6; i++) {
if (size == cap) { // 空间不够就翻倍
cap *= 2;
data = (int *)realloc(data, cap * sizeof(int));
}
data[size++] = i * i;
}
for (int i = 0; i < size; i++) {
printf("%d ", data[i]);
}
printf("\n容量 %d\n", cap);
free(data);
return 0;
}
💡 学习建议:内存管理三问——这块内存是谁申请的?谁负责释放?释放后指针置空了吗?每次写完动态内存代码都自查一遍,养成肌肉记忆。