前言
为什么要有动态内存分配?
可以回想一下目前为止,我们想要向内存申请一块空间存放数据,有哪些手段呢?
目前我们遇到的方式主要有两种:
一、创建一个变量。比如我们存放一个整型数据,就创建一个整型变量。(也就是申请4个字节)我们创建一个变量,存放了一个数据。
int n = 2077;
二、如果我们要存放多个数据呢?这就是我们的数组,连续存储一组相同类型数据。比如下面这里申请了4个整型,也就是16个字节的空间。
int arr[4]={2,0,7,7};
但是,这两种存储方式是有缺陷的:
我们能否将n的4个字节空间扩大到10个字节、将arr的16个字节扩大到40个字节?(缩小也是同理)这是做不到的。这些空间开辟好就固定了,大小是不能变化的。
那么,不能变大变小,在实际解决问题过程中就会存在缺陷:
所以,C语言引入了动态内存开辟,让程序员可以自己申请和释放空间,想扩大缩小都可以,就比较灵活了。
这样的过程是动态的,顾名思义。
而实现动态内存管理的,就是四个函数:malloc 、free 、calloc、 realloc。
那么接下来就是这四个函数的介绍。
malloc函数
对于记忆一个函数名称,理解它的字母本身的含义是非常重要的:
m代表着memory,内存。alloc是allocate,分配、划拨。也就是开辟内存的意思。
头文件是stdlib.h。
参数size就是我们要开辟多大的内存块,单位是字节。
返回值类型是void*,malloc帮我们开辟完空间,交给我们的方式就是把这个空间的起始地址交给我们。
注意,开辟空间的大小不是我们想多大就可以多大的。如果我们要开辟的空间过大,可能就会开辟失败。而开辟失败,这个函数就会返回空指针。
所以这个函数的返回应该有两种情况:起始地址或空指针。
int* p = (int*)malloc(10*sizeof(int));
我们无需自己计算,而是用sizeof的方式来计算10个整型的大小;同时,我们既然希望是把这块空间用于存放整型,我们就通过强制类型转换,最终用一个整型指针来接收这块空间的起始地址。
而根据我们刚才所说,malloc开辟空间可能有两种情况,所以我们要对空指针的情况进行检查:
检查空指针情况
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if(p = NULL)
{
perror("malloc");//perror请看下文简介
return 1;//返回0代表正常情况,非0值代表异常
}
return 0;
}
perror函数
这个函数能将错误对应的错误信息直接打印出来,原型是void perror(const char* str);它会先打印str指向的字符串,然后帮你加上":"和一个空格,再打印错误信息。
malloc申请空间的使用
言归正传,malloc申请的也是一块连续的空间,所以我们可以将其视作数组一般,在我们申请的40个字节里,存上10个整型:
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if(p = NULL)
{
perror("malloc");//perror请看下文简介
return 1;//返回0代表正常情况,非0值代表异常
}
int i = 0;
for(i = 0;i < 10;i++)
{
*(p + i)=i + 1;//将1~10赋给10个整型的空间
}
return 0;
}
但是我们要清楚,这样开辟的空间与数组开辟的空间的区别:
1.动态内存的大小是可以调整的。2.开辟空间的位置不同。
看这张图,是否还记得内存分为这几个区域。
可以看到我们的数组是放在栈区的,而动态内存,也就是我们malloc free calloc realloc操作的空间是在堆区。
最后还要注意的一点是,如果在使用malloc时,参数size的大小为0,那么malloc的行为是未定义的,取决于编译器。
free函数
上面我们做的是申请空间的动作,但其实在申请和使用完之后是需要释放空间的。
在C语言中,free函数就是专门用来做动态内存的释放和回收的。
可以看到,头文件是stdlib.h,原型是void free(void* ptr);
返回类型为void,无需返回值。
参数是void*类型的指针,可以将任意类型地址的空间释放。
所以在刚才我们的代码中,只需在使用完空间后写上一句free(p);就能释放了。
free调用后是把这块空间还给操作系统了,但是p仍然存放着这块空间的地址。
此时我们的p就变成了野指针(p指向的空间不属于当前程序,但还是能找到这个空间)。
就像你将房子租给一个房客,在他退租后仍然拥有这个房子的钥匙能够访问这个空间一样,十分危险。
所以我们要收走这个“钥匙”,也就是p=NULL;
free(p);
p=NULL;
为什么要free
此时我们还能看到数组与malloc申请空间的另一个不同。
请看数组的生命周期的情况:
int main()
{
int arr1[10];
{
int arr2[5];
}//出了这里,arr2生命周期就结束了
return 0;
}//出了这里arr1生命周期就结束了
注意,局部变量(数组)是进入范围创建,出了范围就自动销毁了,也就是将这块空间还给操作系统了。这种空间的生命周期的维护是自动的。但malloc申请的空间就不一样,需要手动(代码方式)释放内存。
如果不释放的话,程序结束时也会被操作系统自动回收。
似乎不释放也会由操作系统“擦屁股”,但是在我们不使用这块空间后可能还有很多代码,而我们没有释放这块空间,这块空间既不用也不释放,有点浪费空间了。所以我们不用时应该释放。
而且,在后面我们想要释放的时候可能找不到这块空间了。
使用free有两种要注意的情况:
1.如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。
2.如果参数ptr是NULL指针,则函数什么事都不做。
比如ptr是指向数组的指针时,不是动态开辟的,这是不能用free释放的。数组在栈上,而free负责的是堆区的空间。
小结
根据上面的讲解,malloc是开辟一块空间,free是释放一块空间,它俩最好成对使用。
calloc函数
calloc也是用来动态内存分配的。
返回类型仍然是void*,而有两个参数。
可以看到,这个函数是开辟空间并且初始化为0。
总结就是这个函数为你开辟num个size大小的空间,把起始地址返回,同时会将内存内容初始化为0。
比如我们现在申请10个整型大小的空间(下面的malloc用于对比):
int* p1 = (int*)calloc(10,sizeof(int));
int* p2 = (int*)malloc(10*sizeof(int));
同样的,calloc开辟空间也可能失败,也会返回NULL,所以我们同样需要对NULL情况进行检查:
if(p == NULL)
{
perror("calloc");
return 1;
}
如果检查不是NULL,代表开辟成功,那么这块空间就可以使用。为了体现calloc开辟的特性,我们就把这块空间的值进行打印:
int i = 0;
for(i=0;i<10;i++)
{
printf("%d ",p[i]);//*(p+i)
}
free(p);//使用完也要释放
p=NULL;//别忘了,否则p变为野指针
使用完要记得释放空间和把p置为NULL。
可以看到,打印的结果就是10个0,说明calloc申请来的空间确实会被初始化为0。
malloc没有初始化的动作,效率略高一些。
realloc函数
re是再的意思,所以realloc是调整空间的意思。我们前面说了动态内存开辟来的空间可以变大变小,而变大变小依靠的就是realloc函数。
原型是void* realloc(void* ptr,size_t size);
我们再看看参数:
可以看到,ptr指向的是被malloc calloc realloc先前开辟过的空间。(所以realloc是调整)
size就是你需要调整为的空间大小。
返回的是调整之后的内存起始位置。
现在我们可以在刚才calloc开辟空间的基础上进行调整,将其变大为20个整型的空间:
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
p=(int*)realloc(p, 20 * sizeof(int));//有问题
free(p);
p = NULL;
}
但这样写,是有问题的,我们不能直接用p来接收realloc的返回值。
在知道为什么之前,我们需要先清楚realloc到底是怎么扩大空间的:
realloc函数在申请空间时,有两种情况:
一、后面后续的空间尚未分配:那么realloc返回旧的地址(也就是参数部分传入的地址)。
二、后面后续的空间已被分配:
1.realloc会在内存的堆区中找一块新的空间,这块空间的大小就是我们想要的新的大小(也就是realloc第二个参数size的大小);
2.会将旧的数据,拷贝到新的空间;
3.会释放旧的空间(不用手动free了);
4.最后,返回新的地址。
特殊情况:也有可能扩容失败,返回NULL。
因为有这种特殊情况的存在,所以我们直接将realloc的返回值赋给p不是很好的做法,因为如果开辟失败p变为NULL,我们原本p指向空间的数据也找不到了。
int main()
{
int* p = (int*)calloc(10, sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
//p=(int*)realloc(p, 20 * sizeof(int));有问题
int* ptr = (int*)realloc(p, 20 * sizeof(int));
if (ptr != NULL)
{
p = ptr;//如果ptr不是空指针,就再赋给p
}
free(p);
p = NULL;
}
注意,realloc第一个参数ptr不能乱传,必须是动态开辟的起始地址,不能传动态开辟空间的中间某个位置,也不能传非动态开辟的空间地址。
到此,本篇博客的内容就全部结束了,祝阅读愉快^_^
标签:10,malloc,管理,int,realloc,free,C语言,动态内存,空间 From: https://blog.csdn.net/2301_82135086/article/details/139335976