C语言动态内存分配

概述

本文档详细介绍C语言中动态内存分配的使用方法,包括malloccallocreallocfree函数的使用,以及动态数组、动态结构体数组的创建和使用,内存泄漏的预防方法等内容。动态内存分配是C语言中重要的功能之一,它允许程序在运行时根据需要分配和释放内存,提高了内存的使用效率。

动态内存分配的基本概念

静态内存与动态内存的区别

特性 静态内存 动态内存
分配时机 编译时 运行时
分配位置 栈区 堆区
大小限制 较小,受栈空间限制 较大,受系统内存限制
生命周期 作用域内 手动分配和释放
灵活性 固定大小,不可调整 可根据需要调整大小

动态内存分配函数

C语言提供了以下动态内存分配函数:

函数 功能 原型 示例
malloc 分配指定大小的内存 void *malloc(size_t size) int *ptr = (int *)malloc(5 * sizeof(int))
calloc 分配指定数量和大小的内存并初始化为0 void *calloc(size_t nmemb, size_t size) int *ptr = (int *)calloc(5, sizeof(int))
realloc 重新分配内存大小 void *realloc(void *ptr, size_t size) ptr = (int *)realloc(ptr, 10 * sizeof(int))
free 释放已分配的内存 void free(void *ptr) free(ptr)

malloc函数示例

函数说明

malloc函数用于分配指定大小的内存空间,返回一个指向分配内存的指针。如果内存分配失败,返回NULL

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// malloc函数示例
void malloc_example() {
int *ptr;
int n = 5;

// 分配5个int类型的内存空间
ptr = (int *)malloc(n * sizeof(int));

if (ptr == NULL) {
printf("内存分配失败!\n");
return;
}

// 初始化并打印分配的内存
printf("使用malloc分配的内存:\n");
for (int i = 0; i < n; i++) {
ptr[i] = i * 10;
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 释放内存
free(ptr);
printf("内存已释放!\n");
}

补充示例:分配不同类型的内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 分配不同类型的内存
void malloc_different_types() {
// 分配整型内存
int *int_ptr = (int *)malloc(5 * sizeof(int));
if (int_ptr != NULL) {
for (int i = 0; i < 5; i++) {
int_ptr[i] = i;
}
printf("分配的整型内存:");
for (int i = 0; i < 5; i++) {
printf("%d ", int_ptr[i]);
}
printf("\n");
free(int_ptr);
}

// 分配浮点型内存
float *float_ptr = (float *)malloc(3 * sizeof(float));
if (float_ptr != NULL) {
float_ptr[0] = 1.1;
float_ptr[1] = 2.2;
float_ptr[2] = 3.3;
printf("分配的浮点型内存:");
for (int i = 0; i < 3; i++) {
printf("%.1f ", float_ptr[i]);
}
printf("\n");
free(float_ptr);
}

// 分配字符型内存
char *char_ptr = (char *)malloc(10 * sizeof(char));
if (char_ptr != NULL) {
strcpy(char_ptr, "Hello");
printf("分配的字符型内存:%s\n", char_ptr);
free(char_ptr);
}
}

calloc函数示例

函数说明

calloc函数用于分配指定数量和大小的内存空间,并将所有字节初始化为0。返回一个指向分配内存的指针。如果内存分配失败,返回NULL

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// calloc函数示例
void calloc_example() {
int *ptr;
int n = 5;

// 分配5个int类型的内存空间,并初始化为0
ptr = (int *)calloc(n, sizeof(int));

if (ptr == NULL) {
printf("内存分配失败!\n");
return;
}

// 打印分配的内存(calloc会初始化为0)
printf("使用calloc分配的内存(初始化为0):\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 初始化并重新打印
for (int i = 0; i < n; i++) {
ptr[i] = i * 20;
}

printf("\n初始化后:\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 释放内存
free(ptr);
printf("内存已释放!\n");
}

补充示例:使用calloc创建二维数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 使用calloc创建二维数组
void create_2d_array() {
int rows = 3, cols = 4;
int **array;

// 分配行指针
array = (int **)calloc(rows, sizeof(int *));
if (array == NULL) {
printf("内存分配失败!\n");
return;
}

// 分配每行的内存
for (int i = 0; i < rows; i++) {
array[i] = (int *)calloc(cols, sizeof(int));
if (array[i] == NULL) {
// 内存分配失败,释放已分配的内存
for (int j = 0; j < i; j++) {
free(array[j]);
}
free(array);
printf("内存分配失败!\n");
return;
}
}

// 初始化二维数组
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
array[i][j] = i * cols + j;
}
}

// 打印二维数组
printf("二维数组内容:\n");
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}

// 释放内存
for (int i = 0; i < rows; i++) {
free(array[i]);
}
free(array);
printf("内存已释放!\n");
}

realloc函数示例

函数说明

realloc函数用于重新分配已分配内存的大小,可以扩大或缩小内存块。如果内存分配失败,返回NULL,但原内存块仍然有效。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
// realloc函数示例
void realloc_example() {
int *ptr;
int n = 5;

// 首先使用malloc分配内存
ptr = (int *)malloc(n * sizeof(int));

if (ptr == NULL) {
printf("内存分配失败!\n");
return;
}

// 初始化内存
for (int i = 0; i < n; i++) {
ptr[i] = i * 5;
}

printf("初始分配5个int的内存:\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 重新分配内存为10个int
n = 10;
ptr = (int *)realloc(ptr, n * sizeof(int));

if (ptr == NULL) {
printf("内存重新分配失败!\n");
return;
}

// 初始化新分配的内存
for (int i = 5; i < n; i++) {
ptr[i] = i * 5;
}

printf("\n重新分配10个int的内存:\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 释放内存
free(ptr);
printf("内存已释放!\n");
}

补充示例:使用realloc缩小内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 使用realloc缩小内存
void shrink_memory() {
int *ptr;
int n = 10;

// 首先分配10个int的内存
ptr = (int *)malloc(n * sizeof(int));

if (ptr == NULL) {
printf("内存分配失败!\n");
return;
}

// 初始化内存
for (int i = 0; i < n; i++) {
ptr[i] = i * 10;
}

printf("初始分配10个int的内存:\n");
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");

// 缩小内存为5个int
n = 5;
ptr = (int *)realloc(ptr, n * sizeof(int));

if (ptr == NULL) {
printf("内存重新分配失败!\n");
free(ptr);
return;
}

printf("缩小为5个int的内存:\n");
for (int i = 0; i < n; i++) {
printf("%d ", ptr[i]);
}
printf("\n");

// 释放内存
free(ptr);
printf("内存已释放!\n");
}

动态数组示例

函数说明

动态数组是使用动态内存分配创建的数组,其大小可以在运行时根据需要调整。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 动态数组示例
void dynamic_array() {
int n;
float *scores;
float sum = 0.0;

// 从用户输入获取数组大小
printf("请输入学生数量:");
scanf("%d", &n);

// 分配内存
scores = (float *)malloc(n * sizeof(float));

if (scores == NULL) {
printf("内存分配失败!\n");
return;
}

// 输入分数
for (int i = 0; i < n; i++) {
printf("请输入学生 %d 的分数:", i + 1);
scanf("%f", &scores[i]);
sum += scores[i];
}

// 计算并打印平均分
float average = sum / n;
printf("\n学生平均分数:%.2f\n", average);

// 打印所有分数
printf("所有学生分数:\n");
for (int i = 0; i < n; i++) {
printf("%.2f ", scores[i]);
}
printf("\n");

// 释放内存
free(scores);
printf("内存已释放!\n");
}

补充示例:动态数组排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 动态数组排序
void sort_dynamic_array() {
int n;
int *array;

// 从用户输入获取数组大小
printf("请输入数组大小:");
scanf("%d", &n);

// 分配内存
array = (int *)malloc(n * sizeof(int));

if (array == NULL) {
printf("内存分配失败!\n");
return;
}

// 输入数组元素
for (int i = 0; i < n; i++) {
printf("请输入元素 %d:", i + 1);
scanf("%d", &array[i]);
}

// 打印原始数组
printf("\n原始数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", array[i]);
}
printf("\n");

// 使用冒泡排序对数组排序
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (array[j] > array[j + 1]) {
// 交换元素
int temp = array[j];
array[j] = array[j + 1];
array[j + 1] = temp;
}
}
}

// 打印排序后的数组
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", array[i]);
}
printf("\n");

// 释放内存
free(array);
printf("内存已释放!\n");
}

动态结构体数组示例

函数说明

动态结构体数组是使用动态内存分配创建的结构体数组,其大小可以在运行时根据需要调整。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
// 学生结构体
struct Student {
char name[50];
int age;
float score;
};

// 动态结构体数组示例
void dynamic_struct_array() {
int n;
struct Student *students;

// 从用户输入获取数组大小
printf("请输入学生数量:");
scanf("%d", &n);

// 分配内存
students = (struct Student *)malloc(n * sizeof(struct Student));

if (students == NULL) {
printf("内存分配失败!\n");
return;
}

// 清空输入缓冲区
while (getchar() != '\n');

// 输入学生信息
for (int i = 0; i < n; i++) {
printf("\n请输入学生 %d 的信息:\n", i + 1);
printf("姓名:");
fgets(students[i].name, sizeof(students[i].name), stdin);
// 移除fgets添加的换行符
students[i].name[strcspn(students[i].name, "\n")] = '\0';

printf("年龄:");
scanf("%d", &students[i].age);

printf("分数:");
scanf("%f", &students[i].score);

// 清空输入缓冲区
while (getchar() != '\n');
}

// 打印学生信息
printf("\n=== 学生信息列表 ===\n");
for (int i = 0; i < n; i++) {
printf("\n学生 %d:\n", i + 1);
printf("姓名:%s\n", students[i].name);
printf("年龄:%d\n", students[i].age);
printf("分数:%.2f\n", students[i].score);
}

// 释放内存
free(students);
printf("\n内存已释放!\n");
}

补充示例:动态结构体数组的排序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 动态结构体数组的排序
void sort_students() {
int n;
struct Student *students;

// 从用户输入获取数组大小
printf("请输入学生数量:");
scanf("%d", &n);

// 分配内存
students = (struct Student *)malloc(n * sizeof(struct Student));

if (students == NULL) {
printf("内存分配失败!\n");
return;
}

// 清空输入缓冲区
while (getchar() != '\n');

// 输入学生信息
for (int i = 0; i < n; i++) {
printf("\n请输入学生 %d 的信息:\n", i + 1);
printf("姓名:");
fgets(students[i].name, sizeof(students[i].name), stdin);
// 移除fgets添加的换行符
students[i].name[strcspn(students[i].name, "\n")] = '\0';

printf("年龄:");
scanf("%d", &students[i].age);

printf("分数:");
scanf("%f", &students[i].score);

// 清空输入缓冲区
while (getchar() != '\n');
}

// 按分数排序(降序)
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (students[j].score < students[j + 1].score) {
// 交换结构体
struct Student temp = students[j];
students[j] = students[j + 1];
students[j + 1] = temp;
}
}
}

// 打印排序后的学生信息
printf("\n=== 按分数排序后的学生信息 ===\n");
for (int i = 0; i < n; i++) {
printf("\n学生 %d:\n", i + 1);
printf("姓名:%s\n", students[i].name);
printf("年龄:%d\n", students[i].age);
printf("分数:%.2f\n", students[i].score);
}

// 释放内存
free(students);
printf("\n内存已释放!\n");
}

内存泄漏预防示例

函数说明

内存泄漏是指程序在运行过程中分配了内存,但在不再需要时没有释放,导致内存资源浪费。内存泄漏会随着程序的运行时间增长而累积,最终可能导致程序崩溃。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 内存泄漏预防示例
void memory_leak_prevention() {
printf("演示内存泄漏预防方法:\n");

// 正确的内存分配和释放方式
printf("\n1. 总是检查内存分配是否成功\n");
printf("2. 每个malloc/calloc/realloc对应一个free\n");
printf("3. 避免在函数中分配内存而不释放\n");
printf("4. 使用free后将指针设为NULL,避免野指针\n");

// 示例:正确的内存使用方式
int *ptr = (int *)malloc(5 * sizeof(int));

if (ptr != NULL) {
// 使用内存
for (int i = 0; i < 5; i++) {
ptr[i] = i;
}

// 释放内存后将指针设为NULL
free(ptr);
ptr = NULL;
printf("\n内存已正确释放,指针已设为NULL!\n");
}

// 错误示例:内存泄漏(注释掉了free)
// int *leaky_ptr = (int *)malloc(5 * sizeof(int));
// leaky_ptr[0] = 100;
// // 忘记调用free(leaky_ptr); // 导致内存泄漏

printf("\n注意:注释掉的代码演示了内存泄漏的情况!\n");
}

补充示例:内存泄漏的常见场景

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// 内存泄漏的常见场景
void memory_leak_scenarios() {
printf("内存泄漏的常见场景:\n");

// 场景1:函数中分配内存但不释放
printf("\n1. 函数中分配内存但不释放:\n");
// void leaky_function() {
// int *ptr = (int *)malloc(10 * sizeof(int));
// // 没有free(ptr);
// }

// 场景2:条件分支中忘记释放内存
printf("\n2. 条件分支中忘记释放内存:\n");
// void conditional_leak() {
// int *ptr = (int *)malloc(10 * sizeof(int));
// if (some_condition) {
// return; // 忘记释放ptr
// }
// free(ptr);
// }

// 场景3:使用realloc时的内存泄漏
printf("\n3. 使用realloc时的内存泄漏:\n");
// void realloc_leak() {
// int *ptr = (int *)malloc(10 * sizeof(int));
// // 错误:如果realloc失败,ptr将为NULL,原内存丢失
// ptr = (int *)realloc(ptr, 20 * sizeof(int));
// free(ptr);
// }

// 正确的realloc使用方式
printf("\n正确的realloc使用方式:\n");
int *ptr = (int *)malloc(10 * sizeof(int));
if (ptr != NULL) {
int *temp = (int *)realloc(ptr, 20 * sizeof(int));
if (temp != NULL) {
ptr = temp;
// 使用ptr
} else {
// 处理realloc失败的情况
printf("realloc失败,但原内存仍然有效\n");
}
free(ptr);
printf("内存已正确释放\n");
}

// 场景4:循环中重复分配内存
printf("\n4. 循环中重复分配内存:\n");
// void loop_leak() {
// char *buffer;
// for (int i = 0; i < 10; i++) {
// buffer = (char *)malloc(100 * sizeof(char));
// // 没有free(buffer);
// }
// }
}

安全内存分配示例

函数说明

安全内存分配是指在分配内存时采取一系列措施,确保内存分配的安全性和可靠性,避免内存泄漏和其他内存相关问题。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// 安全内存分配示例
void safe_memory_allocation() {
printf("演示安全内存分配:\n");

// 使用临时指针来避免内存泄漏
int *temp_ptr = (int *)malloc(10 * sizeof(int));
int *ptr = NULL;

if (temp_ptr != NULL) {
ptr = temp_ptr;

// 使用内存
for (int i = 0; i < 10; i++) {
ptr[i] = i * 2;
}

// 打印内容
printf("\n安全分配的内存内容:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
} else {
printf("\n内存分配失败!\n");
}

// 释放内存
if (ptr != NULL) {
free(ptr);
ptr = NULL;
printf("\n内存已安全释放!\n");
}

// 演示动态字符串分配
printf("\n演示动态字符串分配:\n");

char *str = (char *)malloc(20 * sizeof(char));

if (str != NULL) {
strcpy(str, "Hello, Dynamic!");
printf("动态字符串:%s\n", str);

// 重新分配更大的内存
char *temp_str = (char *)realloc(str, 30 * sizeof(char));

if (temp_str != NULL) {
str = temp_str;
strcat(str, " This is reallocated!");
printf("重新分配后的字符串:%s\n", str);
}

// 释放内存
free(str);
str = NULL;
printf("字符串内存已释放!\n");
}
}

补充示例:安全的内存分配封装函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
// 安全的内存分配封装函数
void *safe_malloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败:无法分配 %zu 字节\n", size);
exit(EXIT_FAILURE);
}
return ptr;
}

// 安全的calloc封装函数
void *safe_calloc(size_t nmemb, size_t size) {
void *ptr = calloc(nmemb, size);
if (ptr == NULL) {
fprintf(stderr, "内存分配失败:无法分配 %zu 个 %zu 字节的元素\n", nmemb, size);
exit(EXIT_FAILURE);
}
return ptr;
}

// 安全的realloc封装函数
void *safe_realloc(void *ptr, size_t size) {
void *new_ptr = realloc(ptr, size);
if (new_ptr == NULL) {
fprintf(stderr, "内存重新分配失败:无法分配 %zu 字节\n", size);
// 注意:原ptr仍然有效
exit(EXIT_FAILURE);
}
return new_ptr;
}

// 使用安全内存分配函数
void use_safe_alloc() {
printf("使用安全内存分配函数:\n");

// 使用safe_malloc
int *ptr = (int *)safe_malloc(5 * sizeof(int));
for (int i = 0; i < 5; i++) {
ptr[i] = i * 10;
}

printf("使用safe_malloc分配的内存:\n");
for (int i = 0; i < 5; i++) {
printf("%d ", ptr[i]);
}
printf("\n");

// 使用safe_realloc
ptr = (int *)safe_realloc(ptr, 10 * sizeof(int));
for (int i = 5; i < 10; i++) {
ptr[i] = i * 10;
}

printf("使用safe_realloc重新分配的内存:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", ptr[i]);
}
printf("\n");

// 使用safe_calloc
float *float_ptr = (float *)safe_calloc(3, sizeof(float));
printf("使用safe_calloc分配的内存(初始化为0):\n");
for (int i = 0; i < 3; i++) {
printf("%.1f ", float_ptr[i]);
}
printf("\n");

// 释放内存
free(ptr);
free(float_ptr);
printf("内存已释放!\n");
}

动态内存分配的高级应用

动态内存分配在链表中的应用

链表是一种常见的数据结构,它使用动态内存分配来存储和管理数据。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// 链表节点结构体
struct Node {
int data;
struct Node *next;
};

// 创建新节点
struct Node *create_node(int data) {
struct Node *new_node = (struct Node *)malloc(sizeof(struct Node));
if (new_node == NULL) {
printf("内存分配失败!\n");
return NULL;
}
new_node->data = data;
new_node->next = NULL;
return new_node;
}

// 向链表末尾添加节点
void append_node(struct Node **head, int data) {
struct Node *new_node = create_node(data);
if (new_node == NULL) {
return;
}

if (*head == NULL) {
*head = new_node;
return;
}

struct Node *temp = *head;
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = new_node;
}

// 打印链表
void print_list(struct Node *head) {
struct Node *temp = head;
while (temp != NULL) {
printf("%d -> ", temp->data);
temp = temp->next;
}
printf("NULL\n");
}

// 释放链表内存
void free_list(struct Node *head) {
struct Node *temp;
while (head != NULL) {
temp = head;
head = head->next;
free(temp);
}
printf("链表内存已释放!\n");
}

// 链表示例
void linked_list_example() {
struct Node *head = NULL;

// 添加节点
append_node(&head, 10);
append_node(&head, 20);
append_node(&head, 30);
append_node(&head, 40);

// 打印链表
printf("链表内容:\n");
print_list(head);

// 释放链表内存
free_list(head);
}

补充示例:动态字符串处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
// 动态字符串处理
void dynamic_string_example() {
// 动态分配字符串
char *str = (char *)malloc(20 * sizeof(char));
if (str != NULL) {
strcpy(str, "Hello");
printf("初始字符串:%s\n", str);
printf("字符串长度:%zu\n", strlen(str));
printf("分配的内存大小:%zu 字节\n", 20 * sizeof(char));

// 扩展字符串
char *temp = (char *)realloc(str, 50 * sizeof(char));
if (temp != NULL) {
str = temp;
strcat(str, " World! This is a longer string.");
printf("扩展后的字符串:%s\n", str);
printf("字符串长度:%zu\n", strlen(str));
printf("分配的内存大小:%zu 字节\n", 50 * sizeof(char));
}

// 释放内存
free(str);
printf("字符串内存已释放!\n");
}

// 示例:读取变长输入
printf("\n读取变长输入:\n");
printf("请输入一段文本:\n");

// 初始分配
size_t buffer_size = 20;
char *input = (char *)malloc(buffer_size * sizeof(char));
if (input != NULL) {
size_t length = 0;
int c;

// 读取输入,直到遇到换行符
while ((c = getchar()) != '\n' && c != EOF) {
if (length >= buffer_size - 1) {
// 扩展缓冲区
buffer_size *= 2;
char *temp = (char *)realloc(input, buffer_size * sizeof(char));
if (temp == NULL) {
printf("内存分配失败!\n");
free(input);
return;
}
input = temp;
}
input[length++] = c;
}
input[length] = '\0';

printf("你输入的文本:%s\n", input);
printf("文本长度:%zu\n", length);
printf("分配的内存大小:%zu 字节\n", buffer_size * sizeof(char));

// 释放内存
free(input);
printf("输入缓冲区已释放!\n");
}
}

内存分配的最佳实践

  1. 始终检查内存分配是否成功:使用malloccallocrealloc函数后,一定要检查返回值是否为NULL
  2. 每个分配对应一个释放:确保每个malloccallocrealloc调用都有对应的free调用
  3. 使用free后将指针设为NULL:避免野指针,防止重复释放内存
  4. 使用临时指针进行realloc:避免realloc失败时丢失原内存
  5. 合理使用内存:不要分配过多的内存,避免内存浪费
  6. 释放所有分配的内存:特别是在函数返回前,确保释放所有分配的内存
  7. 注意内存对齐:某些系统对内存对齐有要求,使用malloc等函数会自动处理
  8. 使用封装函数:创建安全的内存分配封装函数,简化错误处理
  9. 监控内存使用:对于大型程序,考虑使用内存监控工具
  10. 避免内存碎片:频繁分配和释放小块内存可能导致内存碎片

综合示例

示例:动态内存分配的学生管理系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// 学生管理系统
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 学生结构体
typedef struct {
char name[50];
int id;
float score;
} Student;

// 学生管理系统结构体
typedef struct {
Student *students;
int count;
int capacity;
} StudentManager;

// 初始化学生管理系统
StudentManager *init_manager(int initial_capacity) {
StudentManager *manager = (StudentManager *)malloc(sizeof(StudentManager));
if (manager == NULL) {
return NULL;
}

manager->students = (Student *)malloc(initial_capacity * sizeof(Student));
if (manager->students == NULL) {
free(manager);
return NULL;
}

manager->count = 0;
manager->capacity = initial_capacity;

return manager;
}

// 添加学生
void add_student(StudentManager *manager, const char *name, int id, float score) {
// 检查是否需要扩展容量
if (manager->count >= manager->capacity) {
int new_capacity = manager->capacity * 2;
Student *new_students = (Student *)realloc(manager->students, new_capacity * sizeof(Student));
if (new_students == NULL) {
printf("内存分配失败,无法添加学生!\n");
return;
}
manager->students = new_students;
manager->capacity = new_capacity;
printf("容量扩展到:%d\n", new_capacity);
}

// 添加学生
strcpy(manager->students[manager->count].name, name);
manager->students[manager->count].id = id;
manager->students[manager->count].score = score;
manager->count++;

printf("学生添加成功!\n");
}

// 打印所有学生
void print_students(StudentManager *manager) {
if (manager->count == 0) {
printf("没有学生信息!\n");
return;
}

printf("\n=== 学生信息列表 ===\n");
for (int i = 0; i < manager->count; i++) {
printf("\n学生 %d:\n", i + 1);
printf("姓名:%s\n", manager->students[i].name);
printf("ID:%d\n", manager->students[i].id);
printf("分数:%.2f\n", manager->students[i].score);
}
}

// 查找学生
Student *find_student(StudentManager *manager, int id) {
for (int i = 0; i < manager->count; i++) {
if (manager->students[i].id == id) {
return &manager->students[i];
}
}
return NULL;
}

// 释放学生管理系统
void free_manager(StudentManager *manager) {
if (manager != NULL) {
if (manager->students != NULL) {
free(manager->students);
}
free(manager);
printf("学生管理系统内存已释放!\n");
}
}

// 测试学生管理系统
void test_student_manager() {
// 初始化管理系统,初始容量为2
StudentManager *manager = init_manager(2);

if (manager == NULL) {
printf("初始化学生管理系统失败!\n");
return;
}

// 添加学生
add_student(manager, "张三", 1001, 95.5);
add_student(manager, "李四", 1002, 88.0);
add_student(manager, "王五", 1003, 92.5); // 这里会触发容量扩展
add_student(manager, "赵六", 1004, 85.0);

// 打印学生信息
print_students(manager);

// 查找学生
int search_id = 1002;
Student *student = find_student(manager, search_id);
if (student != NULL) {
printf("\n找到学生 ID %d:\n", search_id);
printf("姓名:%s\n", student->name);
printf("分数:%.2f\n", student->score);
} else {
printf("\n未找到学生 ID %d\n", search_id);
}

// 释放管理系统
free_manager(manager);
}

总结

C语言中的动态内存分配是实现灵活内存管理的重要手段,通过本文档的学习,我们了解了:

  1. 动态内存分配的基本概念:静态内存与动态内存的区别,动态内存分配函数的使用
  2. malloc函数:分配指定大小的内存
  3. calloc函数:分配指定数量和大小的内存并初始化为0
  4. realloc函数:重新分配内存大小
  5. 动态数组:使用动态内存分配创建的数组
  6. 动态结构体数组:使用动态内存分配创建的结构体数组
  7. 内存泄漏的预防:如何避免内存泄漏
  8. 安全内存分配:如何安全地分配和使用内存
  9. 动态内存分配的高级应用:链表、动态字符串等
  10. 内存分配的最佳实践:检查内存分配是否成功、释放内存、避免野指针等

掌握动态内存分配对于编写高效、灵活的C程序至关重要,它允许程序在运行时根据需要分配和释放内存,提高了内存的使用效率。通过合理使用动态内存分配函数,我们可以编写出更加灵活、功能强大的C程序。

dynamic_memory.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/**
* @brief 动态内存分配示例程序
* @details 这个程序演示了C语言中动态内存分配的各种用法,包括
* malloc、calloc、realloc和free函数的使用,以及动态数组、
* 动态结构体数组和内存泄漏的避免方法。
* @return 0 表示程序成功执行
*/

// =========================
// 结构体定义
// =========================

/**
* @brief 学生结构体
*/
struct Student {
char name[50];
int age;
float score;
};

// =========================
// 函数原型声明
// =========================
void malloc_example(); // malloc函数示例
void calloc_example(); // calloc函数示例
void realloc_example(); // realloc函数示例
void dynamic_array(); // 动态数组示例
void dynamic_struct_array(); // 动态结构体数组示例
void memory_leak_prevention(); // 内存泄漏预防示例
void safe_memory_allocation(); // 安全内存分配示例

/**
* @brief 主函数
* @return 0 表示程序成功执行
*/
int main() {
// =========================
// malloc函数示例
// =========================
printf("=== malloc函数示例 ===\n");
malloc_example();

printf("\n");

// =========================
// calloc函数示例
// =========================
printf("=== calloc函数示例 ===\n");
calloc_example();

printf("\n");

// =========================
// realloc函数示例
// =========================
printf("=== realloc函数示例 ===\n");
realloc_example();

printf("\n");

// =========================
// 动态数组示例
// =========================
printf("=== 动态数组示例 ===\n");
dynamic_array();

printf("\n");

// =========================
// 动态结构体数组示例
// =========================
printf("=== 动态结构体数组示例 ===\n");
dynamic_struct_array();

printf("\n");

// =========================
// 内存泄漏预防示例
// =========================
printf("=== 内存泄漏预防示例 ===\n");
memory_leak_prevention();

printf("\n");

// =========================
// 安全内存分配示例
// =========================
printf("=== 安全内存分配示例 ===\n");
safe_memory_allocation();

return 0;
}

/**
* @brief malloc函数示例
* @details 演示使用malloc函数分配内存
*/
void malloc_example() {
int *ptr;
int n = 5;

// 分配5个int类型的内存空间
ptr = (int *)malloc(n * sizeof(int));

if (ptr == NULL) {
printf("内存分配失败!\n");
return;
}

// 初始化并打印分配的内存
printf("使用malloc分配的内存:\n");
for (int i = 0; i < n; i++) {
ptr[i] = i * 10;
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 释放内存
free(ptr);
printf("内存已释放!\n");
}

/**
* @brief calloc函数示例
* @details 演示使用calloc函数分配内存
*/
void calloc_example() {
int *ptr;
int n = 5;

// 分配5个int类型的内存空间,并初始化为0
ptr = (int *)calloc(n, sizeof(int));

if (ptr == NULL) {
printf("内存分配失败!\n");
return;
}

// 打印分配的内存(calloc会初始化为0)
printf("使用calloc分配的内存(初始化为0):\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 初始化并重新打印
for (int i = 0; i < n; i++) {
ptr[i] = i * 20;
}

printf("\n初始化后:\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 释放内存
free(ptr);
printf("内存已释放!\n");
}

/**
* @brief realloc函数示例
* @details 演示使用realloc函数重新分配内存
*/
void realloc_example() {
int *ptr;
int n = 5;

// 首先使用malloc分配内存
ptr = (int *)malloc(n * sizeof(int));

if (ptr == NULL) {
printf("内存分配失败!\n");
return;
}

// 初始化内存
for (int i = 0; i < n; i++) {
ptr[i] = i * 5;
}

printf("初始分配5个int的内存:\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 重新分配内存为10个int
n = 10;
ptr = (int *)realloc(ptr, n * sizeof(int));

if (ptr == NULL) {
printf("内存重新分配失败!\n");
return;
}

// 初始化新分配的内存
for (int i = 5; i < n; i++) {
ptr[i] = i * 5;
}

printf("\n重新分配10个int的内存:\n");
for (int i = 0; i < n; i++) {
printf("ptr[%d] = %d\n", i, ptr[i]);
}

// 释放内存
free(ptr);
printf("内存已释放!\n");
}

/**
* @brief 动态数组示例
* @details 演示如何创建和使用动态数组
*/
void dynamic_array() {
int n;
float *scores;
float sum = 0.0;

// 从用户输入获取数组大小
printf("请输入学生数量:");
scanf("%d", &n);

// 分配内存
scores = (float *)malloc(n * sizeof(float));

if (scores == NULL) {
printf("内存分配失败!\n");
return;
}

// 输入分数
for (int i = 0; i < n; i++) {
printf("请输入学生 %d 的分数:", i + 1);
scanf("%f", &scores[i]);
sum += scores[i];
}

// 计算并打印平均分
float average = sum / n;
printf("\n学生平均分数:%.2f\n", average);

// 打印所有分数
printf("所有学生分数:\n");
for (int i = 0; i < n; i++) {
printf("%.2f ", scores[i]);
}
printf("\n");

// 释放内存
free(scores);
printf("内存已释放!\n");
}

/**
* @brief 动态结构体数组示例
* @details 演示如何创建和使用动态结构体数组
*/
void dynamic_struct_array() {
int n;
struct Student *students;

// 从用户输入获取数组大小
printf("请输入学生数量:");
scanf("%d", &n);

// 分配内存
students = (struct Student *)malloc(n * sizeof(struct Student));

if (students == NULL) {
printf("内存分配失败!\n");
return;
}

// 清空输入缓冲区
while (getchar() != '\n');

// 输入学生信息
for (int i = 0; i < n; i++) {
printf("\n请输入学生 %d 的信息:\n", i + 1);
printf("姓名:");
fgets(students[i].name, sizeof(students[i].name), stdin);
// 移除fgets添加的换行符
students[i].name[strcspn(students[i].name, "\n")] = '\0';

printf("年龄:");
scanf("%d", &students[i].age);

printf("分数:");
scanf("%f", &students[i].score);

// 清空输入缓冲区
while (getchar() != '\n');
}

// 打印学生信息
printf("\n=== 学生信息列表 ===\n");
for (int i = 0; i < n; i++) {
printf("\n学生 %d:\n", i + 1);
printf("姓名:%s\n", students[i].name);
printf("年龄:%d\n", students[i].age);
printf("分数:%.2f\n", students[i].score);
}

// 释放内存
free(students);
printf("\n内存已释放!\n");
}

/**
* @brief 内存泄漏预防示例
* @details 演示如何预防内存泄漏
*/
void memory_leak_prevention() {
printf("演示内存泄漏预防方法:\n");

// 正确的内存分配和释放方式
printf("\n1. 总是检查内存分配是否成功\n");
printf("2. 每个malloc/calloc/realloc对应一个free\n");
printf("3. 避免在函数中分配内存而不释放\n");
printf("4. 使用free后将指针设为NULL,避免野指针\n");

// 示例:正确的内存使用方式
int *ptr = (int *)malloc(5 * sizeof(int));

if (ptr != NULL) {
// 使用内存
for (int i = 0; i < 5; i++) {
ptr[i] = i;
}

// 释放内存后将指针设为NULL
free(ptr);
ptr = NULL;
printf("\n内存已正确释放,指针已设为NULL!\n");
}

// 错误示例:内存泄漏(注释掉了free)
// int *leaky_ptr = (int *)malloc(5 * sizeof(int));
// leaky_ptr[0] = 100;
// // 忘记调用free(leaky_ptr); // 导致内存泄漏

printf("\n注意:注释掉的代码演示了内存泄漏的情况!\n");
}

/**
* @brief 安全内存分配示例
* @details 演示如何安全地分配和使用内存
*/
void safe_memory_allocation() {
printf("演示安全内存分配:\n");

// 使用临时指针来避免内存泄漏
int *temp_ptr = (int *)malloc(10 * sizeof(int));
int *ptr = NULL;

if (temp_ptr != NULL) {
ptr = temp_ptr;

// 使用内存
for (int i = 0; i < 10; i++) {
ptr[i] = i * 2;
}

// 打印内容
printf("\n安全分配的内存内容:\n");
for (int i = 0; i < 10; i++) {
printf("%d ", ptr[i]);
}
printf("\n");
} else {
printf("\n内存分配失败!\n");
}

// 释放内存
if (ptr != NULL) {
free(ptr);
ptr = NULL;
printf("\n内存已安全释放!\n");
}

// 演示动态字符串分配
printf("\n演示动态字符串分配:\n");

char *str = (char *)malloc(20 * sizeof(char));

if (str != NULL) {
strcpy(str, "Hello, Dynamic!");
printf("动态字符串:%s\n", str);

// 重新分配更大的内存
char *temp_str = (char *)realloc(str, 30 * sizeof(char));

if (temp_str != NULL) {
str = temp_str;
strcat(str, " This is reallocated!");
printf("重新分配后的字符串:%s\n", str);
}

// 释放内存
free(str);
str = NULL;
printf("字符串内存已释放!\n");
}
}