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 不初始化内存,内容是垃圾值。分配后务必检查返回值是否为 NULLfree 后将指针置 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 栈地址是未定义行为。

📖 小节

📝 作业

  1. 编写程序:用户输入 n,动态分配 n 个 int,读入 n 个数后排序输出,最后释放内存
  2. 编写动态二维数组函数:int **create_matrix(int rows, int cols)void free_matrix(int **m, int rows),在 main 中创建 4×5 矩阵并赋值打印
  3. 故意在代码中制造一个内存泄漏,用 valgrind(或 Dr. Memory)检测并观察输出
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏