文章目录
- 一、动态内存管理是什么
- 二、动态内存管理相关函数
- 1. malloc
- 2. free
- 3. calloc
- 4. realloc
- 三、柔性数组
- 1. 概念
- 2. 使用
一、动态内存管理是什么
我们之前已经知道,定义变量就是申请一块空间,int a;
就是申请四个字节的空间,char arr[20]
就是申请20个字节的空间。这样的空间申请方式有两个特点:空间开辟的大小是固定的、开辟好的空间大小不能再修改了。但是对于内存空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行时才能知道,那么这种申请内存空间的方式就不能满足我们的需求的
所以,C语言中引入了动态内存管理(开辟),让我们能自由地申请和释放内存空间。
而要实现这一点,必须要依靠下面的相关函数。
二、动态内存管理相关函数
以下四个函数malloc、free、calloc、realloc,都包含在头文件stdlib.h中。
1. malloc
C语言提供了一个动态内存开辟的函数malloc:
这个函数能申请一块大小为size个字节的内存空间,返回指向这块空间的指针。
但要注意的是:
- 内存空间可能会开辟失败,如果失败了,会返回NULL空指针,因此使用malloc后一定要对返回值做检查。
- malloc并不知道开辟空间的存放数据类型是什么,返回指针的类型是
void*
。所以一般需要使用者对返回值进行强制类型转换 - 参数size为0,malloc的行为是未定义的,结果取决于编译器。
2. free
C语言还提供了一个函数free,是用来实现动态内存的释放和回收的:
free能将参数指针指向的空间进行释放和回收,free后该指针变成野指针。以前也讲过,这时最好把野指针置为NULL。
但要注意的是:
- 如果参数ptr指向的空间不是动态开辟的,那么函数free的行为是未定义的。
- 如果参数ptr是NULL空指针,那么free函数什么都不做
在一个程序中,如果开辟的动态空间不用free释放回收,操作系统也会在程序退出的时候自动回收这块内存;但一般情况下,我们动态开辟好的空间在使用完后,习惯性free掉,为了节省空间。
举个栗子:用malloc申请一块空间,存放num个int数据,打印出来,再free掉这块空间。
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{int num = 0;scanf("%d",&num);int* p = NULL;p = (int*)malloc(num*sizeof(int));assert(p != NULL);//检查是否空间开辟成功for(int i=0 ; i<num ; i++)scanf("%d", p+i);for(int i=0 ; i<num ; i++)printf("%d ", *(p+i));free(p);p = NULL;//有必要的,牢记出现野指针随即置为NULLreturn 0;
}
3. calloc
还有一个函数是calloc,也是用来进行动态内存分配的:
这个函数的功能是为num个size大小的数据开辟一块空间,并且把每个字节初始化为0。它与函数malloc的区别只在于calloc能将每个字节初始化为0,其余的使用方法与malloc完全相同。
int* p = (int*)calloc(10, sizeof(int));
assert(p != NULL);
for(int i=0 ; i<10 ; i++)printf("%d ",*(p+i));
free(p);
p = NULL;
如果要求我们对申请的内存空间的内容初始化,那么应该使用calloc。
4. realloc
在申请空间后,有时我们发现申请好的空间太小了,有时又觉得太大了,为了合理地使用内存,我们会对已经开辟的内存大小做一些调整,relloc函数是用来实现这个功能的。
参数指针ptr代表着要调整的空间,size是调整后想要的大小。函数返回指向调整后的空间的指针。当然,也可能开辟空间失败,返回空指针NULL。
realloc调整空间大小有两种情况:
- 情况1:原空间之后有足够大的空间
- 情况2:原空间之后没有足够大的空间
对于这两种情况:
- 情况1:扩展内存直接从原空间之后追加。
- 情况2:在堆空间上另外找一个大小合适的连续空间来使用,将原空间中的已有数据拷贝到新空间内,释放回收原空间。
有了这个函数,我们就可以在写项目的过程中,实时调整要使用的内存空间的大小。
很好理解吧~
三、柔性数组
1. 概念
C99中,结构体中的最后一个元素允许是未知大小的数组,这就是柔性数组。
例如:
struct s
{int i;char c;int a[];
}
要注意的是:
- 结构体中的柔性数组前面必须有至少一个其他成员,柔性数组必须是结构体的最后一个成员
- 结构体的大小计算不会算上柔性数组的大小
- 包含柔性数组的结构体需要用malloc函数进行内存分配,并且分配的内存应该大于结构体的大小,适应柔性数组的预期使用大小。
2. 使用
举个栗子:
struct s
{char c;int a[];
};struct s* p = (struct s*)malloc(sizeof(struct s)+100*sizeof(int));
assert(p!=NULL);
p->c = 'x';
for(int i=0 ; i<100 ; i++)p->a[i] = i;
free(p);
p = NULL;
这样,结构体成员c获得了值’x’,结构体成员柔性数组a获得了100个整型元素的连续空间并存入了一些数据。
有人可能会有疑惑,即使不使用柔性数组,在结构体中定义一个指针,在这个指针指向的空间,在开辟足够的大小来存储数据,也可以达到我们的目的。那么使用柔性数组的优势是什么?
好处是:
- 方便内存回收释放。使用柔性数组,在结构体使用完毕后,我们只需进行一次free就可以把所有开辟的空间释放掉。但是不使用柔性数组的话,我们需要free两次,一次释放结构体空间,一次释放结构体成员指针开辟的空间。
- 有利于提高访问速度。连续的内存有利于提高访问速度,也有利于减少内存碎片。
本篇完,感谢阅读~