C: 动态内存管理
动态内存就像租房:需要时申请(malloc),用完退租(free),不退就持续占资源——这就是内存泄漏。
1. 为什么需要动态内存
栈上的局部变量在函数返回后就没了,数组大小必须在编译时确定。当数据量运行时才知晓,或者需要跨函数长期持有数据,就得用堆上的动态内存。
C
int n;
scanf("%d", &n);
int arr[n];
⚠️ 变长数组(VLA)是 C99 特性,不是所有编译器都支持,也不推荐用于大数组。正式做法是用
malloc。
2. malloc 与 free
malloc 在堆上分配指定字节数的内存,返回 void *。用完必须 free。
C
int *p = (int *)malloc(sizeof(int) * 5);
if (p == NULL) {
printf("内存分配失败\n");
return 1;
}
for (int i = 0; i < 5; i++) {
p[i] = i * 10;
}
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
free(p);
p = NULL;
TEXT
📖 仅展示
0 10 20 30 40
💡
malloc 不初始化内存,内容是垃圾值。分配后务必检查返回值是否为 NULL。free 后将指针置 NULL 是好习惯,防止悬空指针。
3. calloc
calloc 分配内存并将每一位初始化为 0。参数是元素个数和每个元素的大小。
C
int *p = (int *)calloc(5, sizeof(int));
if (p) {
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
free(p);
}
TEXT
📖 仅展示
0 0 0 0 0
malloc vs calloc:
| 函数 | 初始化 | 参数 |
|---|---|---|
malloc |
不初始化 | 总字节数 |
calloc |
清零 | 元素个数, 每个元素大小 |
4. realloc
realloc 调整已分配内存的大小,可以扩大也可以缩小。
C
int *p = (int *)malloc(sizeof(int) * 3);
p[0] = 10; p[1] = 20; p[2] = 30;
int *tmp = (int *)realloc(p, sizeof(int) * 5);
if (tmp) {
p = tmp;
p[3] = 40;
p[4] = 50;
for (int i = 0; i < 5; i++) {
printf("%d ", p[i]);
}
free(p);
}
TEXT
📖 仅展示
10 20 30 40 50
⚠️
realloc 可能返回新地址(原数据自动拷贝),也可能返回原地址。不要直接 p = realloc(p, ...) ——如果失败返回 NULL,原指针就丢了。用临时变量接收。
5. 动态一维数组
▶ 示例
C
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n;
printf("输入元素个数: ");
scanf("%d", &n);
int *arr = (int *)calloc(n, sizeof(int));
if (!arr) return 1;
for (int i = 0; i < n; i++) {
arr[i] = (i + 1) * (i + 1);
}
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr);
arr = NULL;
return 0;
}
TEXT
📖 仅展示
输入元素个数: 5
1 4 9 16 25
6. 动态二维数组
动态二维数组有两种常见实现方式。
(1) 方式一:指针数组(每行独立分配)
C
int rows = 3, cols = 4;
int **matrix = (int **)malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
matrix[i] = (int *)calloc(cols, sizeof(int));
}
matrix[1][2] = 99;
printf("%d\n", matrix[1][2]);
for (int i = 0; i < rows; i++) {
free(matrix[i]);
}
free(matrix);
TEXT
📖 仅展示
99
(2) 方式二:连续内存(一次分配)
C
int rows = 3, cols = 4;
int *buf = (int *)calloc(rows * cols, sizeof(int));
int **matrix = (int **)malloc(sizeof(int *) * rows);
for (int i = 0; i < rows; i++) {
matrix[i] = buf + i * cols;
}
matrix[2][3] = 77;
printf("%d\n", matrix[2][3]);
free(matrix);
free(buf);
TEXT
📖 仅展示
77
💡 方式二内存连续,缓存友好,释放也简单。方式一每行可以有不同的列数(不规则数组)。
▶ 示例
C
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int rows = 3, cols = 4;
int **m = (int **)malloc(sizeof(int *) * rows);
int *buf = (int *)calloc(rows * cols, sizeof(int));
for (int i = 0; i < rows; i++) {
m[i] = buf + i * cols;
}
int val = 1;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
m[i][j] = val++;
}
}
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%3d", m[i][j]);
}
printf("\n");
}
free(m);
free(buf);
return 0;
}
TEXT
📖 仅展示
1 2 3 4
5 6 7 8
9 10 11 12
7. 常见动态内存错误
(1) 忘记 free——内存泄漏
C
void leak(void) {
int *p = (int *)malloc(sizeof(int) * 100);
}
函数结束,p 这个局部变量没了,但那 100 个 int 的堆内存还占着,再也无法释放。
(2) 重复 free
C
int *p = (int *)malloc(sizeof(int));
free(p);
free(p);
⚠️ 对同一块内存
free 两次是未定义行为,可能导致程序崩溃。free 后立即置 NULL,再 free(NULL) 是安全的。
(3) 使用已释放的内存
C
int *p = (int *)malloc(sizeof(int));
*p = 42;
free(p);
printf("%d\n", *p);
⚠️ 释放后再访问是悬空指针,结果不可预测。
(4) 越界访问
C
int *p = (int *)malloc(sizeof(int) * 5);
p[5] = 100;
分配了 5 个元素,下标 0-4,p[5] 越界。
8. valgrind 简介
Valgrind 是 Linux 下的内存检测工具,能发现内存泄漏、越界、未初始化读取等问题。
BASH
gcc -g -o myapp myapp.c
valgrind --leak-check=full ./myapp
典型输出:
TEXT
📖 仅展示
==12345== HEAP SUMMARY:
==12345== in use at exit: 400 bytes in 1 blocks
==12345== total heap usage: 2 allocs, 1 frees, 800 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==12345== by 0x10915E: leak (myapp.c:5)
💡 Windows 上可使用 Dr. Memory 或 Visual Studio 的调试堆。macOS 可用 Instruments 的 Leaks 工具。
❓ 常见问题
Q malloc 返回的内存里是什么值?
A 不确定——是之前用过的垃圾数据。需要清零就用 calloc,或者手动 memset。
Q free 之后指针还有值吗?
A free 只释放内存,不改变指针的值,它还存着那个地址(悬空指针)。所以 free 后要立刻置 NULL。
Q realloc 扩容时原有数据会丢吗?
A 不会。realloc 保证原有数据被保留(至少 min(旧大小,新大小) 个字节)。如果搬迁到新地址,自动拷贝。
Q 可以 free 栈上的变量吗?
A 绝对不行。free 只能释放 malloc/calloc/realloc 返回的堆内存,free 栈地址是未定义行为。
📖 小节
malloc(size)分配不初始化,calloc(n, size)分配并清零,realloc(ptr, size)调整大小- 动态二维数组推荐"一次分配连续内存 + 指针数组映射行"的方式
- 必须成对使用 malloc/free,忘记 free 导致内存泄漏
- 常见错误:泄漏、重复 free、悬空指针、越界
- valgrind 可自动检测内存问题,开发时应常使用
📝 作业
- 编写程序:用户输入 n,动态分配 n 个 int,读入 n 个数后排序输出,最后释放内存
- 编写动态二维数组函数:
int **create_matrix(int rows, int cols)和void free_matrix(int **m, int rows),在 main 中创建 4×5 矩阵并赋值打印 - 故意在代码中制造一个内存泄漏,用 valgrind(或 Dr. Memory)检测并观察输出