C语言详解(动态内存管理)2
Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言
🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。
目录
- 前言
- 1、常见动态内存错误
- 1.1 对NULL指针的解引用操作
- 1.2 对动态内存空间的越界访问
- 1.3 对非动态开辟内存使用free释放
- 1.4 使用free释放动态内存的一部分
- 1.5 对同一快动态内存多次释放
- 1.6 动态开辟内存忘记释放(内存泄漏)
- 2、动态内存经典笔试题分析
- 2.1 题目一
- 2.2 题目二
- 2.3 题目三
- 2.4 题目四
- 3、柔性数组
- 3.1 什么是柔性数组
- 3.2 柔性数组的特点
- 3.3 柔性数组的使用
- 3.4 柔性数组的优势
- 总结
前言
总的来说,动态内存管理为我们提供了更加灵活、高效和可扩展的内存管理方式,但动态内存管理函数可能会带来一些风险,主要包括内存泄漏、内存溢出和野指针等问题,我们在使用动态内存管理函数时要多留心,避免风险的出现
1、常见动态内存错误
1.1 对NULL指针的解引用操作
如果我们写的代码不严谨,没有考虑到动态内存分配失败的可能,就会写出类似于下面的代码:
#include #include int main() { int* p = (int*)malloc(10 * sizeof(int)); //直接使用指针p int i = 0; for (i = 0; i这样的代码可能并没有什么问题,但是存在很大的隐患,因为动态内存函数是有可能开辟内存空间失败的,当开辟失败时会返回NULL,而NULL指针是不能解引用的
像VS这样比较强大的编译器会立马检测到并提示你
为了避免这种错误,我们需要对指针p进行判断,再决定是否使用
#include #include int main() { int* p = (int*)malloc(10 * sizeof(int)); //判断p是否为空指针 if (p == NULL) { //打印出错误信息 perror("malloc"); //终止程序 return 1; } int i = 0; for (i = 0; i1.2 对动态内存空间的越界访问
我们用动态内存函数开辟多大的空间,我们就使用多大的空间,不能越界访问,例如:
#include #include int main() { int* p = (int*)malloc(10 * sizeof(int)); //判断p是否为空指针 if (p == NULL) { //打印出错误信息 perror("malloc"); //终止程序 return 1; } int i = 0; //p+1跳过1个整型,p+10就会越界 for (i = 0; i p[i] = i + 1; } return 0; } int arr[10] = { 0 }; int* p = arr; free(p); p = NULL; return 0; } int* p = (int*)malloc(10 * sizeof(int)); //判断p是否为空指针 if (p == NULL) { //打印出错误信息 perror("malloc"); //终止程序 return 1; } //给申请的动态空间内存1~10 int i = 0; for (i = 0; i arr[i] = i + 1; } //... free(ps); ps = NULL; return 0; }柔性数组的柔性怎么体现呢?
因为上面包含柔性数组的结构是由malloc函数进行内存的动态分配,所以我们可以使用realloc函数进行动态内存的调整,那这个数组的大小就可大可小
#include #include struct S { int n; int arr[]; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S) + 20 * sizeof(int)); if (ps == NULL) { perror("malloc"); //终止程序 return 1; } //使用空间 ps->n = 100; int i = 0; for (i = 0; i arr[i] = i + 1; } //调整ps指向的空间大小 struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40 * sizeof(int)); //进行指针的非空判断,保护原地址 if (ptr != NULL) { ps = ptr; //防止ptr变成野指针 ptr = NULL; } else { perror("realloc"); //终止程序 return 1; } for (i = 0; i arr[i]); } //... free(ps); ps = NULL; return 0; }如果不使用柔性数组,还有一种办法能实现上面的效果
#include #include struct S { int n; int* arr; }; int main() { struct S* ps = (struct S*)malloc(sizeof(struct S)); if (ps == NULL) { perror("malloc"); return 1; } int* tmp = (int*)malloc(20 * sizeof(int)); if (tmp == NULL) { perror("malloc"); return 1; } else { ps->arr = tmp; tmp = NULL; } ps->n = 100; int i = 0; //给指针arr指向的20个整型空间赋值 for (i = 0; i arr[i] = i + 1; } //调整指针arr指向的空间大小 tmp = (int*)realloc(ps->arr, 40 * sizeof(int)); if (tmp != NULL) { ps->arr = tmp; tmp = NULL; } else { perror("realloc"); return 1; } for (i = 0; i arr[i]); } //... free(ps->arr); ps->arr = NULL; free(ps); ps = NULL; return 0; }结构struct S中有一个指针成员,我们的想法是用malloc函数申请一块动态内存空间,再让结构中的这个指针指向这块动态分配的内存,然后这块由指针指向的动态内存空间就可以用realloc函数进行大小的调整了
可以看到这样实现的效果和柔性数组相似,那柔性数组为什么还要存在呢?
其实相比之下柔性数组还是有它的优势的
3.4 柔性数组的优势
- 方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了两次内存分配,并把整个结构体返回给用户,用户调佣free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事
所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存释放
- 这样有利于访问速度
连续的内存有利于提高访问速度,也有利于减少内存碎片
因为malloc等动态内存函数在申请空间时会在堆区允许的地方申请一块连续的空间,但是动态内存函数申请的多个动态内存空间之间并不是连续的,这些空间之间就形成了内存碎片
总结
- 动态内存管理是一把双刃剑,它能给我们提供灵活的内存管理方式,但同样也会带来风险
- 检查动态内存分配是否成功:在使用动态内存管理函数时,应该检查分配内存是否成功,以确保程序正常运行,这是比较容易忽略的点
- 这样有利于访问速度
- 方便内存释放






