![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/dee7ad04f28a4cba8a6d728f4bf60072.png)
为什么要用动态内存管理
#include<stdio.h>
int main()
{
int arr[20] = {0};
return 0;
}
这样创建数组大小就是固定的,无法进行动态调整大小
所以我们引入了4个动态内存管理的函数
拓展:malloc,calloc,realloc,free在内存中的堆区
局部变量和形式参数在栈区
静态变量和全局变量在静态区
一.malloc函数
- 函数原型:void* malloc(size_t size);
- 申请的是一块连续存放的空间
- malloc向内存申请了字节大小为size的动态的空间
- 如果申请成功返回指向指向这块空间首元素的指针
- 如果申请失败,则返回NULL,所以一定要检查是否为NULL
- size = 0,理论上可行,但是申请0字节没意义
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
perror("malloc");
//给它显示错误,并打印在屏幕上
return 1;
//提前返回
}
//使用
//...
for (int i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
free(p);
//将申请的空间还给操作系统
//也就是将空间释放
p = NULL;
//不使用置为NULL,不置为NULL,是野指针
//野指针很危险
return 0;
}
注意:
malloc函数不对空间中的内容进行初始化,这些内容是随机值
二.calloc函数
- 函数原型:void* calloc(size_t num,size_t size)
malloc和calloc函数非常相似
注意:
唯一的区别是malloc函数在malloc函数会在返回空间地址之前把所有申请的空间的内容初始化为0
int main()
{
int* p = (int*)calloc(5, 20);
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用
//...
for (int i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}
free(p);//把申请空间释放掉,后面会介绍
p = NULL;
return 0;
}
三.realloc函数
- 函数原型:void* realloc(void*str,size_t size)
- realloc函数可以调整开辟完空间后的大小
- str是指向要开辟空间的起始空间,size是调整为size的空间大小(比如原来空间是10字节,size是20,就是将原空间调整为20),返回的指针可以是任何类型
- 返回值与前面一样,如果调整成功,返回开辟好空间的首地址,如果调整失败,则返回NULL
int main()
{
int* p = (int*)calloc(5, 20);
if (p == NULL)
{
perror("calloc");
return 1;
}
//使用
//...
for (int i = 0; i < 5; i++)
{
*(p + i) = i + 1;
}//将开辟好的空间内容赋值
int* str = (int*)realloc(p, 100);
//调整成功(返回值不为NULL),就使用
if (str != NULL)
{
p = str;//使用
for (int i = 0; i < 10; i++)
{
*(p + i) = i + 1;
}
free(p);//释放空间,后面介绍
p = NULL;
str = NULL;
}
else
{
//否则调整失败,打印错误信息
perror("realloc");
free(p);//释放空间,后面介绍
p = NULL;
str = NULL;
}
return 0;
}
注意:
recalloc调整空间大小的时候有3种情况:
1.刚好后面的空间是足够存下调整后的空间的大小的
注意:空间是连续存放的,这里没画好
2.后面的空间不足以装下调整后的空间
所以会在堆区重新找一份新的空间,并且大小符合要求
并且会把原有的20字节的内容拷贝到新开辟的空间中
原有空间会被释放掉
返回新开辟空间的起始地址
红色区域是已使用的空间
3.需要扩展的空间后方没有空间可以扩张,并且堆中也没有符合要求的空间给50字节扩张,所以无法正常开辟空间,会返回NULL
四.free函数
- 函数原型:void free(void* str)
- free函数就是用来释放malloc,realloc,calloc动态开辟的空间的
- free函数释放空间,为了避免内存的泄露(内存的泄露指的是这块内存空间使用过后没有还给操作系统,并且之后也不在使用这个指针,这块空间就一直被占用,周而复始,空间就会越来越少,也会使系统的性能下降)
- 如果传入的NULL,那么free函数什么也不做
free(pf);
pf = NULL;//它的使用就是这么简单
五.常见的动态内存错误
1.对空指针的解引用操作
int* pf = (int*)malloc(20);
*pf = 30;//如果pf是NULL的话,就有问题
//所以要判断pf是不是NULL
2.对动态开辟空间的越界访问
int main()
{
int* pf = (int*)malloc(20);//5个int
if (pf == NULL)
{
perror("malloc");
return 1;
}
//使用
for (int i = 0; i <= 5; i++)//6个int
{
*(pf + i) = i + 1;
}
return 0;
}
检查是否越界访问
3.对非动态开辟的空间使用free函数
int a = 0;
int* p = &a;
free(p);
free函数只能使用在动态开辟的内存释放
4.使用free释放一部分动态开辟的空间
int* pf = (int*)malloc(sizeof(int)*5);
pf++;
free(pf);
pf = NULL;
注意:
free函数释放空间的指针要指向这块空间的起始地址,
才是完整释放动态的空间
- free函数对同一块内存空间的多次释放
int* pf = (int*)malloc(sizeof(int)*5);
free(pf);
free(pf);
要解决这个问题非常简单,只要这个指针不使用了,要把它立即置为NULL,这样再对它进行多次释放,指针为空,free函数为空,什么也不做,就不存在对同一块空间的多次释放
6.动态开辟的内存忘记释放(内存泄露)
void test()
{
int* pf = (int*)malloc(sizeof(int)*5);
if(pf != NULL)
{
*pf = 90;
}
}
int main()
{
test();
return 0;
}
尽量做到谁(函数)申请的空间谁来释放
如果不能释放,记得告诉别人,记得要释放