青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-
友链 搜索文章 >>

青江的个人站

“保持热爱,奔赴星海”

  • 主页
  • 目录
  • 图床
  • 留言板
  • -关于我-

【C语言学习笔记】十三、动态内存分配


阅读数: 0次    2026-03-10
字数:2.8k字 | 预计阅读时长:11分钟

1. 传统数组的问题

指针和动态内存分配是C语言中最重要的两个部分。在C语言中,对内存的管理至关重要。

传统数组占用的内存在定义的那一刻已经被定死了。

如果没有动态内存分配,可能会出现为了满足最大需求而浪费内存的情况。例如,有极少部分用户需要输入很长的数据时,需要为了这部分用户扩大变量所占用的内存,而对于大多数用户来说,这样显然会浪费很多内存空间。

因此以往传统的数组定义形式灵活性低,应用受限。整个程序都依赖于预设编译前的大小,可能不够用,也可能会浪费,内存不够用时可能会有内存溢出,导致一些安全隐患。

这时候就需要动态内存分配,可以处理可变大小的数据,有高效的数据结构和资源优化。

2. ★重点:栈内存和堆内存的对比

计算机操作系统的内存管理机制中,有两种不同的存储区域:栈内存(Stack Memory)与堆内存(Heap Memory)。

这两种内存的不同点包括:分配机制、生命周期、访问速度、用途区分。

栈内存:

  • 有自动管理机制,函数调用时候,局部变量会被分配在栈上,当函数返回时候,局部变量全部销毁释放。
  • 栈内存在编译时候就已经确定好了需要分配的内存空间大小。
  • 栈内存的分配和访问速度通常要比堆内存快,它是一种线性的数据结构。
  • 大小有限制,栈的大小在程序启动那一刹那就已经确定,无法改动。如果栈的内存空间被耗尽,就意味着崩溃(栈溢出)。
  • 一般用于存储函数的局部变量,函数参数,函数调用的返回地址。

堆内存:

  • 需要动态(手动)管理,调用操作内存空间相关函数来手动控制内存的分配与释放。例如,malloc、calloc、realloc、free。
  • 速度相较于栈有点慢,它需要在内存中寻找足够大的连续空间块。
  • 大小灵活,堆的大小通常受到可用系统内存的限制,而非栈本身的限制。它能够支持动态按需分配大量内存。

3. malloc函数动态内存分配的使用与释放

malloc函数传入一个size_t类型的数据,表示要分配的内存大小,单位是字节。返回一个void*类型的指针,在C语言中用于泛型编程,可以转换为任意其他数据类型的指针。

malloc函数分配的内存在堆上。

malloc函数包含在头文件stdlib.h中。

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
#include <stdio.h>
#include <stdlib.h>

int main(void) {
// 传统静态数组
// 编译时候就确定了数组的大小、生命周期和作用域。
// 内存分配在栈上,自动管理内存,使用完毕后会自动释放。
int static_array[5] = { 1, 2, 3, 4, 5 };
printf("Static array: ");
for (size_t i = 0; i < 5; i++) {
printf("%d ", static_array[i]);
}
printf("\n");

// 动态数组
// 运行时动态分配内存,可以根据需要调整大小。
// 内存分配在堆上,需要手动管理内存,使用完毕后需要调用 free() 释放。
int* dynamic_array = (int*)malloc(5 * sizeof(int));
// 检查内存分配是否成功
if (dynamic_array == NULL) {
perror("动态数组分配失败");
exit(EXIT_FAILURE);
}
// 初始化动态数组
for (size_t i = 0; i < 5; i++) {
dynamic_array[i] = (int)(i + 1);
}
printf("Dynamic array: ");
for (size_t i = 0; i < 5; i++) {
printf("%d ", dynamic_array[i]);
}
printf("\n");
// 释放动态数组的内存
free(dynamic_array);

return 0;
}

4. 企业案例:realloc函数与释放

realloc函数用来动态调整已经被分配的内存空间。

参数:

  • ptr:指针指向一个要重新分配内存的内存块,该内存块之前是通过调用malloc、calloc或realloc进行分配内存的。如果之前未被分配,为空指针,则会分配一个新的内存块,且函数返回一个指向它的指针。
  • size:内存块的新的大小,以字节为单位。如果大小为0,且ptr指向一个已存在的内存块,则ptr所指向的内存块会被释放,并返回一个空指针。

返回值:

  • 如果成功,realloc返回指向新内存块的指针。
  • 如果失败,返回NULL,并且原来的内存块仍然保持不变(并没有释放)。

注意:

  • realloc可能会将内存块移动到新的位置(如果在原位置没有足够的空间容纳新的大小)。如果移动成功,ptr会指向新位置,旧的内存空间会被释放。需要特别注意,旧的ptr指针需要被更新为realloc返回的新地址。
  • 如果内存分配失败,realloc返回NULL,而原始的内存块不会被释放。为避免内存泄漏,应该使用一个临时指针来接收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
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>

// 打印部门预算
void printBudgets(double* budgets, uint32_t size);

// 一个简单的动态调整部门预算的程序
int main(void) {
uint32_t size = 3; // 初始部门数量
// 分配内存
double* budgets = (double*)malloc(size * sizeof(double));
if (budgets == NULL) {
perror("Failed to allocate memory");
return EXIT_FAILURE;
}

// 初始化部门预算
budgets[0] = 10000.0;
budgets[1] = 15000.0;
budgets[2] = 20000.0;

// 打印初始预算
printf("Initial budgets:\n");
printBudgets(budgets, size);

// 新的一年,需要增加两个部门
uint32_t newSize = size + 2;
// 重新分配内存
double* newBudgets = (double*)realloc(budgets, newSize * sizeof(double));
if (newBudgets == NULL) {
perror("Failed to reallocate memory");
free(budgets); // 释放原有内存
return EXIT_FAILURE;
}

budgets = newBudgets; // ★ 更新指针

// 初始化新部门预算
budgets[3] = 25000.0;
budgets[4] = 30000.0;

// 打印更新后的预算
printf("Updated budgets:\n");
printBudgets(budgets, newSize);

// 释放内存
free(budgets);

return EXIT_SUCCESS;
}

// 打印部门预算
void printBudgets(double* budgets, uint32_t size) {
for (size_t i = 0; i < size; i++) {
printf("Department %zu budget: $%.2f\n", i + 1, budgets[i]);
}
}

5. malloc与结构体的使用以及防止内存的泄露

使用malloc函数为一个结构体分配内存时,如果结构体内有成员是指针类型,则需要单独为该成员使用malloc函数分配内存空间。

当使用malloc函数为一个结构体分配内存时,只分配了结构体本身的空间,包括所有成员变量所占的内存。

但如果成员中存在指针,malloc函数只为指针变量本身分配了空间,但没有为指针指向的数据分配空间。

此时指针的值是未定义的(野指针),不能直接使用。

需要再次使用malloc函数为成员中的指针也分配地址,才可以安全使用,避免了野指针与内存泄露。

释放内存时,成员与结构体也需要分别释放。

注意:动态内存的分配一定要进行跟踪和管理,分配内存后,必须立即判断内存是否分配成功,并写好对应的卫语句,在合适的时候释放内存。

下面是一个简单的为结构体及部分成员分配内存的案例。

例如创建一个游戏角色。

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
#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

// 角色结构体定义
typedef struct Character {
char* name;
uint32_t level;
uint32_t hp;
} Character;

// 创建一个角色
Character* create_character(const char* name, uint32_t level, uint32_t hp);

// 释放角色占用的内存
void free_character(Character* character);

int main(void) {
Character* hero = create_character("Hero", 1, 100);
if (hero == NULL) {
fprintf(stderr, "Failed to create character\n");
return EXIT_FAILURE; // 创建角色失败
}

printf("Character Name: %s\n", hero->name);
printf("Character Level: %" PRIu32 "\n", hero->level);
printf("Character HP: %" PRIu32 "\n", hero->hp);

free_character(hero); // 释放角色占用的内存

return EXIT_SUCCESS;
}

// 创建一个角色
Character* create_character(const char* name, uint32_t level, uint32_t hp) {
// 为角色分配内存
Character* character = (Character*)malloc(sizeof(Character));
if (character == NULL) {
perror("Failed to allocate memory for character");
return NULL; // 内存分配失败
}

// 为角色名字分配内存
character->name = (char*)malloc(strlen(name) + 1);
if (character->name == NULL) {
perror("Failed to allocate memory for character name");
free(character); // 释放之前分配的内存,防止内存泄漏
return NULL; // 内存分配失败
}
strcpy_s(character->name, strlen(name) + 1, name); // 复制名字
character->level = level;
character->hp = hp;
return character;
}

// 释放角色占用的内存
void free_character(Character* character) {
if (character != NULL) {
free(character->name); // 释放名字占用的内存
free(character); // 释放角色占用的内存
}
}

6. calloc函数

calloc函数的用法与malloc函数类似,但calloc函数不仅可以分配内存,还同时将所有的位置初始化为0。

下面是一个简单的使用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
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
#include <stdio.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

// 定义一个员工结构体
typedef struct Employee {
char* name; // 员工名字
uint32_t* taskList; // 员工任务列表
uint32_t taskCount; // 员工任务数量
} Employee;

// 创建一个员工
Employee* createEmployee(const char* name, uint32_t taskCount);

// 释放员工内存
void freeEmployee(Employee* employee);

int main(void) {
Employee* developer = createEmployee("Alice", 5);
if (developer == NULL) {
fprintf(stderr, "Failed to create employee\n");
return EXIT_FAILURE;
}
developer->taskList[0] = 101; // 给员工分配一个任务
printf("Employee Name: %s\n", developer->name);
printf("First Task ID: %" PRIu32 "\n", developer->taskList[0]);
printf("Second Task ID: %" PRIu32 "\n", developer->taskList[1]); // 使用calloc分配内存,未初始化的元素默认为0
printf("Total Tasks: %" PRIu32 "\n", developer->taskCount);

freeEmployee(developer); // 释放员工内存

return EXIT_SUCCESS;
}

// 创建一个员工
Employee* createEmployee(const char* name, uint32_t taskCount) {
// 分配内存给员工结构体
Employee* employee = (Employee*)malloc(sizeof(Employee));
if (employee == NULL) {
perror("Failed to allocate memory for employee");
return NULL;
}

// 分配内存给员工名字
employee->name = (char*)malloc(strlen(name) + 1);
if (employee->name == NULL) {
perror("Failed to allocate memory for employee name");
free(employee); // 释放员工结构体内存
return NULL;
}

strcpy_s(employee->name, strlen(name) + 1, name); // 复制员工名字

// 分配内存给员工任务列表
employee->taskList = (uint32_t*)calloc(taskCount, sizeof(uint32_t));
if (employee->taskList == NULL) {
perror("Failed to allocate memory for employee task list");
// 注意内存释放的顺序,先释放员工名字内存,再释放员工结构体内存
free(employee->name);
free(employee);
return NULL;
}

employee->taskCount = taskCount; // 设置员工任务数量

return employee;
}

// 释放员工内存
void freeEmployee(Employee* employee) {
if (employee != NULL) {
// 需要先释放成员变量的内存,再释放结构体本身的内存
// 成员变量内存的释放顺序可以任意
free(employee->name); // 释放员工名字内存
free(employee->taskList); // 释放员工任务列表内存
free(employee); // 释放员工结构体内存
}
}
本文来源: 青江的个人站
本文链接: https://hanqingjiang.com/2026/03/10/20260310_C_dynamicMemory/
版权声明: 本作品采用 CC BY-NC-SA 4.0 进行许可。转载请注明出处!
知识共享许可协议
赏

谢谢你请我喝可乐~

支付宝
微信
  • Notes
  • C

扫一扫,分享到微信

微信分享二维码
【C语言学习笔记】十二、常用库函数(数学、时间与错误处理)
  1. 1. 1. 传统数组的问题
  2. 2. 2. ★重点:栈内存和堆内存的对比
  3. 3. 3. malloc函数动态内存分配的使用与释放
  4. 4. 4. 企业案例:realloc函数与释放
  5. 5. 5. malloc与结构体的使用以及防止内存的泄露
  6. 6. 6. calloc函数
© 2021-2026 青江的个人站
晋ICP备2024051277号-1
powered by Hexo & Yilia
  • 友链
  • 搜索文章 >>

tag:

  • 生日快乐🎂
  • 新年快乐!
  • 小技巧
  • Linux
  • 命令
  • 语录
  • 复刻
  • Blog
  • Notes
  • Android
  • C
  • Homework
  • MATLAB
  • FPGA
  • Server
  • Vivado
  • Git

  • 引路人-稚晖
  • Bilibili-稚晖君
  • 超有趣讲师-Frank
  • Bilibili-Frank