【C语言进阶篇】动态内存管理
🌈个人主页:开敲
🔥所属专栏:C语言
🌼文章目录🌼
1. 为什么要有动态内存分配
在我们之前的学习中,掌握的开辟内存的方式有:
1 int a = 0;//在栈上开辟四个字节的空间
2 int arr[10] = {0};//在栈上开辟10个整型(40个字节)的空间
但是上面这种开辟空间的方法有两个特点:
① 开辟空间的大小是固定的
② 数组在声明的时候,一旦数组的大小确定下来了,在编译时就不能改变
但在我们实际的情况中,对于空间的需求不仅仅是上述的情况,实际我们可能在编译的时候才能确定所需空间的大小,这个时候上面这写开辟空间的方式就不适用了。这个时候就需要用到我们灵活(动态)地进行内存地开辟。
C语言引入了动态内存开辟,这使得程序员能够自己根据实际需求来申请和释放空间,使代码更加的灵活。
下面就来介绍几个动态内存开辟的函数和动态内存释放的函数。
2.动态内存开辟和释放函数
这些函数都声明在头文件<stdlib.h>
2.1 动态内存释放函数
2.1.1 free函数
free函数是动态内存管理中必不可少的十分重要的一个函数,用来将动态开辟的内存释放和回收,函数说明如下:
① 如果参数ptr所指向的空间不是动态开辟的,那么free的行为是未定义的
② 如果参数ptr是NULL指针,则函数什么事也不干
函数使用:
2.2 动态内存开辟函数
2.2.1 malloc函数
这个函数向内存申请一块连续的可用地址,并且返回这块地址的指针。size(需要开辟的空间大小,单位是字节)下面是关于这个函数的一些说明:
① 如果开辟成功,则返回一个指向开辟好的空间地址的指针
② 如果开辟失败,则返回一个NULL指针。(因此,malloc的返回值必须进行检查)
③ 返回指针的类型为void*,因此malloc可以返回一个任意类型的指针,具体类型由使用者决定
④ 如果参数size为0,malloc执行的行为是未定义的,具体由编译器决定
函数使用:
2.2.2 calloc函数
calloc函数与malloc函数十分相似,区别在于calloc函数的参数部分多了一个num(需要开辟的元素个数),size(每个元素的大小)并且calloc函数在开辟空间时会将开辟好的空间里的内容初始化为0:
2.2.3 realloc函数
在我们动态开辟内存时,时而会发现申请的空间太小了,时而又会发现申请的空间太大了,那么为了能够合理地使用内存,我们就需要对内存的大小进行调整。realloc函数就可以做到对动态开辟内存大小的调整。
realloc函数的作用是:将动态开辟的空间扩容。
① ptr是要调整的内存的地址
② size是调整之后希望的大小
③ 返回的是调整之后的内存起始地址
④ 在调整好空间后,将原有空间内已经存放的数据拷贝到新的空间中
⑤ realloc在调整内存空间大小时有以下两种情况:
1. 原有空间后有足够大的空间存放扩容后的空间,直接向后开辟新的空间
2. 原有空间后没有足够大的空间,则重新找一块可用的足够大的空间进行开辟,然后返回新空间的地址,同时将原有空间的数据拷贝到新的空间中
由于上述两种情况的存在,在使用realloc函数时必须对realloc返回的指针进行是否为NULL指针的检查。
3. 常见的动态内存的错误
3.1 对NULL指针的解引用操作
1 void text()
2 {
3 int* p = (int*)malloc(INT_MAX);//这里由于需要开辟的空间太大,导致无法正常开辟
4 //空间,malloc返回一个NULL指针
5 *p = 20;//这里的p为NULL指针,对NULL指针解引用操作
6 free(p);
7 }
3.2 对动态开辟内存的越界访问
1 void text()
2 {
3 int i = 0;
4 int* pf = (int*)malloc(10*sizeof(int));
5 if(pf==NULL)
6 {
7 exit(EIXT_FAILURE);
8 }
9 for(i = 0;i<=10;i++) //循环次数为11次
10 {
11 *(p+i) = i;
12 }
13 free(pf);
14 pf = NULL;
15 }
3.3 对非动态开辟的内存free释放
1 void text()
2 {
3 int a = 10;
4 int*p = &a;
5 free(p);
6 }
3.4 使用free释放动态开辟内存的一部分
1 void text()
2 {
3 int* pf = (int*)malloc(5*sizeof(int));
4 pf++; //pf不再指向动态开辟内存的起始地址
5 free(p);
6 }
3.5 对同一块动态内存多次释放
1 void text()
2 {
3 int* pf = (int*)malloc(5*sizeof(int));
4 free(p);
5 free(p);
6 }
3.6 动态内存开辟忘记释放(内存泄漏)
这是动态内存错误中最严重的一个错误
1 void text()
2 {
3 int* pf = (int*)malloc(5*sizeof(int));
4 if(pf != NULL)
5 {
6 *p = 20;
7 }
8 }
9
10 int main()
11 {
12 text();
13 while(1);//这里再出函数之后直接进入了死循环,函数内也没有释放内存,导致
14 //开辟的这块空间无法释放,也无法使用,导致内存泄漏
15 }
切记:动态开辟的内存一定要释放,并且正确释放!
4. 动态内存经典笔试题练习
题目1:
题目2:
题目3:
题目4:
5. 柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但它确实是存在的。
C99中,结构体中的最后一个元素是位置大小的数组,这个数组就是柔性数组。例如:
1 typedef struct st_sype
2 {
3 int i = 0;
4 int a[ ];
5 }type_a;
5.1 柔性数组的特点
① 结构体中的柔性数组成员前必须有至少一个其它成员(保证柔性数组是结构体的最后一个成员)
② sizeof在计算这种结构体时,不会算入柔性数组的大小
③ 包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应当大于sizeof计算结构体的大小,以保证能够满足柔性数组的预期大小。
例如:
1 typedef struct st_type
2 {
3 int i;
4 int a[0];//柔性数组成员
5 }type_a;6
7 int main()
8 {
9 printf("%d\n", sizeof(type_a));//输出的是4
10 return 0;
11 }
5.2 柔性数组的使用
1 //代码1
2 #include <stdio.h>
3 #include <stdlib.h>
4 int main()
5 { int i = 0;
6 type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
7 //业务处理
8 p->i = 100;
9 for(i=0; i<100; i++)
10 {
11 p->a[i] = i;
12 }
13 free(p);
14 return 0;
15 }
这样柔性数组成员a相当于获得了100个整型元素的连续空间。
上述的type_a结构也可以设计为下面的结构,也能完成相同的效果。
1 //代码2
2 #include <stdio.h>
3 #include <stdlib.h>4
5 typedef struct st_type
6 {
7 int i;
8 int *p_a;
9 }type_a;
10 int main()11 {
12 type_a *p = (type_a *)malloc(sizeof(type_a));
13 p->i = 100;
14 p->p_a = (int *)malloc(p->i*sizeof(int));
15 //业务处理
16 for(i=0; i<100; i++)
17 {
18 p->p_a[i] = i;
19 }
20 //释放空间
21 free(p->p_a);
22 p->p_a = NULL;
23 free(p);24 p = NULL;
25 return 0;
26 }
5.3 柔性数组的优势
上述代码1和代码2可以完成相同的功能,但是代码1有两个好处:
①:方便内存释放
如果我们的代码是在⼀个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给用户⼀个结构体指针,用户做⼀次free就可以把所有的内存也给释放掉。
②:有利于访问速度
连续的内存有益于提高访问速度以及提高内存的利用率,也有益于减少内存碎片,提高内存的利用率。
创作不易,点个赞呗,谢谢啦~