动态内存管理
为什么存在动态内存管理?
int a=10;//在栈空间上面开辟四个字节
char arr[10]= {0};----在栈空间上开辟10个字节的连续空间
这是我们目前所掌握的开辟空间的方式
1、空间开辟的大小是固定的
2、数组在声明的时候,必须要指定数组的长度,它所需要的内存在编译阶段就分配
这种固定开辟空间的方式就有了局限性,不方便后期进行空间上的维护
动态内存函数
#include<stdlib.h>
malloc
void* malloc (size_t size);
分配一个字节的内存块,返回指向该块开头的指针
1、如果开辟成功,返回一个指向开辟空间的指针
2、申请空间可能会申请失败,如果开辟失败,返回的是空指针,因此malloc的返回值一定要检查有效性
3、返回值的类型是void*类型,所以malloc函数并不知道开辟空间的类型,具体在使用的时候由使用者来决定
int arr[10]={0};
int *p = (int *)malloc(40);
if(p==NULL)
{
printf("%s\n",strerror(errno));
return 1;----历史习惯:return 0是正常返回,return 1就是异常返回
}
----当程序退出的时候,系统会自动回收内存空间,并不是说 内存空间就不回收了
free
void free (void* ptr);
free函数用来释放动态开辟的内存的
1、如果不是动态开辟的内存,是不能够用free来释放的
2、如果参数ptr是空指针,则函数什么事情都不会做
free是释放动态开辟的内存,然而p还记得free以前内存的地址,但此时如果再访问p指向的内存地址,会造成对非法空间访问的问题,也就演变成了野指针的问题,所以,对于动态开辟的内存释放以后,置为空指针是必须的。
calloc
void* calloc (size_t num, size_t size);
函数是开辟num个大小为size的空间
与malloc函数的区别就是calloc会在返回地址之前把申请到的空间的每个字节全部初始化为0
也就是相当于calloc = malloc +memset
总结一句话,如果想要把申请到的内存空间初始化,就用calloc
如果不想初始化,就用malloc
realloc
void* realloc (void* ptr, size_t size);
1、ptr为要调整的内存地址
2、size为调整后的新大小
3、返回值为调整之后的内存地址
realloc函数调整内存空间的时候会有两种情况
1、原有空间后面没有足够大的空间
2、原有空间后面有足够大的空间1
int *ptr= malloc(20);
int *tmp =(int*) realloc(ptr,40);
1、没有足够空间,在堆空间其他位置找一个足够大小的空间,将原数据拷贝到这40个字节的空间中,并返回新开辟空间的起始地址
2、有足够空间,直接在后面追加20个字节的空间
不能直接用ptr来接收,如果要开辟的空间过大,堆上没办法开辟足够大的空间,realloc函数就要返回空指针,那样ptr就变成了空指针,以前的空间也找不到了
柔性数组
在C99标准中,结构中的最后一个元素允许是未知大小的数组
struct Stu
{
int i;
int arr[0];
}
在结构体成员中,最后一个成员是未知大小的数组成员,并且在柔型数组前面至少有一个元素
在计算大小的时候,只计算了柔性数组前面其他的成员大小
柔性数组的特点:
1、结构中的柔性数组前面至少有一个其他成员
2、sizeof返回的结构大小并不会包括柔性数组的内存
3、包含柔性数组成员的结构应该用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
🌰栗子:
struct Stu s;----不这样创建,这样的话只能存放4个字节,没有给柔性数组开辟空间
柔性数组的使用
int i=0;
struct Stu* p = (struct Stu*)malloc(sizeof(struct Stu)+10*sizeof(int));
if(p==NULL)
{
return 1;
}
p->i=100;
for(i=0;i<100;i++)
{
p->arr[i] = i;
}
free(p);
获得了100个整型元素的连续空间
如何柔性?可以通过realloc来维护空间
int i=0;
struct Stu* p = (struct Stu*)malloc(sizeof(struct Stu)+10*sizeof(int));
if(p==NULL)
{
return 1;
}
p->i=100;
for(i=0;i<10;i++)
{
p->arr[i] = i;
}
struct Stu* ptr = (struct Stu*)realloc(p,sizeof(struct Stu)+80);----扩容
if(p!=NULL)
{
ps=ptr;
ptr=NULL;
}
free(ps);
ps=NULL;
那肯定会 有人问到了,为什么不能直接用int*类型的指针来维护一块动态内存空间呢?
struct S
{
int n;
int* arr;
};
int main()
{
struct S*ps = (struct S*)malloc(sizeof(struct S));----因为动态开辟的内存是在堆区,所以我们把int类型的变量也创建在堆区
if (ps == NULL)
{
return 1;
}
ps->n = 100;
ps->arr = (int*)malloc(40);
if (ps->arr == NULL)
{
//....
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i]);
}
//扩容
int*ptr = (int*)realloc(ps->arr, 80);
if (ptr == NULL)
{
return 1;
}
else
{
ps->arr = ptr;//补充:如果扩容成功,这里要讲ptr的值赋值给ps->arr,空间依然由ps->arr维护
}
//使用
//释放
free(ps->arr);
free(ps);
ps = NULL;
return 0;
}
如果用int类型的指针开辟一块空间,第二种方法做了两次动态内存分配,在进行free释放结构体的时候,用户释放结构体,但不知道这个结构体中的成员也需要free,如果我们把结构体的内存以及成员需要的内存一次性分配好,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存释放掉。
并且,多次进行malloc还会造成内存碎片化。