一、前言
C语言中有很多开辟内存的方式C阶段,用的最多的也就是数组。但是,我们每次用数组时只能设定一个固定的值,开辟一个固定的内存大小,就比如在写通讯录的时候,你定义了一个存放100个人的信息的数组,当我们存入人的信息很少时,就会浪费很大的空间,存入人的信息很多时,空间又不够用。而且此时空间开辟的大小又是固定的,不能修改,很不方便。假如我们想要先开辟一块小的内存,之后不够用时再开辟一块更大的内存,不想用时再将这块空间回收。这就既没有了空间的浪费,又可以让存放很多信息时更方便的开辟内存空间。那该怎么解决呢?这时候就要用到动态内存管理来解决了!
二、动态内存函数的介绍
这里需要讲到四个函数:malloc 、free 、calloc 、realloc
下面我们对其一一讲解:
1、malloc函数和free函数
(1)malloc函数
首先我们可以看到malloc的参数是size_ szie,表示申请多少个字节的空间,并且会指向这块空间的起始地址。
假如我们申请40个字节的内存空间,那么malloc返回值会指向内存空间的起始地址:
接着我们可以看到malloc返回值类型是void*类型,为什么是这样呢?是因为我们在开辟内存空间时,malloc函数并不知道你要存入内存空间的是什么类型,所以我们在使用malloc函数时,需要先搞清楚我们存入内存空间的变量的类型。
接下来我们打开编译器,去实操认识malloc函数:
使用malloc函数需要包括对应的头文件#inlcude<stdlib.h>。十个整型需要四十个字节,整型是int,所以我们需要将malloc返回值强制转化为int*存放到int*p中
这时候我们就已经申请了40个字节大小的内存空间了。
我们再看malloc函数的返回值:
如果malloc函数申请内存成功,则返回申请到的空间的起始地址
如果申请失败,则返回NULL指针
综上,malloc函数是不一定会申请成功的,那么我们怎么才能知道它是否申请成功或者失败了呢?我们用perror函数来查看malloc的申请错误原因,当perror收到NULL时就会自动打印出malloc函数的错误原因。所以,以防万一,我们都需要用perror函数来检查malloc函数是否申请成功。如下:
我举个申请失败的例子:
这里的INT_MAX,是整型最大值,这里乘4只是为了让它更大。我们调试可知:
Not enough space,没有足够的空间。这也告诉我们用malloc函数时检查其是否申请成功的一个重要性。
既然我们在需要用空间时可以申请内存空间,那么当我们不用时也可以回收这个空间,C语言提供了另外一个函数free,专门是用来做动态内存的释放和回收的。
在讲free函数之前,我们先用一下我们申请的内存空间:
我们将0~9这十个数字存放到我们刚申请的十个空间内。方便之后free函数讲解
在这里我讲解一下*(p+1)=i 这步,也许有的小伙伴们会有疑惑,为什么要这样呢?
我们上面已经讲了,如果malloc函数申请空间成功,则返回这片空间的起始地址,我们存放到p中,p存放的就是这片空间的起始地址。我们p+1 ,那么就会访问第i个地址,在对其解引用,存放i。打印出结果为:
那malloc函数申请的空间,是怎样释放的呢?
(2)free函数
1、free释放——主动释放
2、程序退出后,malloc申请的空间,也会被操作系统回收的——被动回收
正常情况下,谁申请的空间,谁去释放 ,万一我们忘记释放,也会被别人释放,这是一种较为危险的操作。所以我们最好自己free主动释放。
接下来我们来认识free函数:
free函数参数是void*的一个指针,返回类型为void,也就是说这个函数不需要返回。它来释放动态开辟的内存,并且只能释放动态开辟的内存,ptr就是我们要释放的那块空间的起始地址。
我们将我们刚刚开辟的内存空间释放并调试查看p:
结果我放在这里,对比看出,释放前后p中的地址却没有变化,这是为什么呢?
因为free释放的内存空间的大小,p中还时会有地址存放,那p有地址却没内存空间,那么p就成为了一个野指针,一个具有危险性的野指针。
所以我们一般free之后,我们会将p赋值成空指针。如下:
至此,动态内存开辟函数malloc与动态内存释放函数free就讲完了。
2、calloc函数
C语言还提供了一个函数交calloc,calloc函数也用来动态内存分配。原型如下:
calloc函数比malloc函数多了一个参数size_t num,是我们需要申请num个size大小的空间。
calloc申请空间成功也会返回空间的起始地址,申请失败也会返回NULL指针
calloc函数与malloc函数的区别:
看到这个我们总觉得和malloc函数没什么差别,无非malloc需要我们自己算,而calloc不需要
比如:
让人觉得只是少了一个算的步骤,除此之外没什么区别。其实不然,calloc函数申请好空间之后,会将空间初始化为0,malloc不会,这就是它们的区别。
我们打开编译器来看一下它们的区别:
malloc:
calloc:
以上我们就可以看出,malloc函数不会将申请的空间初始化,而calloc函数会。
有人也会疑惑,那究竟应该用哪个呢?
取决于你,如果你申请的空间需要初始化,你就可以选择用calloc,如果不需要,那你就可以选择malloc
3、realloc函数
realloc函数的出现让动态内存管理更加灵活,有时我们发现过去申请的空间太小了,有时候又觉得过大了,那为了合理的使用内存,我们一定会对内存的大小进行调整。那realloc函数就可以做到。
函数原型如下:
ptr是要调整的内存的起始地址,size是调整之后的新大小。
并且这个函数在调整原内存空间大小的基础上,将原来内存中的数据移动到新的空间。
既然内存已经被调整,那它的起始地址也会改变,所以realloc函数的返回值是调整之后内存的起始位置。如果失败的话仍然会返回NULL指针。但是这里有个注意的地方:返回NULL指针不就把原本来的内存空间存放的信息丢失了吗?
我们就先来打开编译器去认识realloc函数应该怎么使用:
当我想要调整空间为20个整型的空间时,使用realloc函数,需要调整的内存的起始地址是p。
我们将调整好的起始地址放入pf中,是因为我用原来的p接收,当realloc开辟失败的时候,会返回NULL指针,p就会变成NULL指针,这样会将原来内存空间存放的数据丢失。所以为了避免这种情况,我们先用另外一个指针pf接收,进而判断pf是否为空指针,如果不是,那就说明realloc函数开辟内存空间成功了,我们便可以再将pf赋给p,继续用p来完成。
那么realloc他是怎么调整内存空间的呢?
realloc调整内存空间是存在两种情况:
情况一:原有空间之后有足够大的空间。
第一种很好理解,后面如果有足够的空间的话,我们直接再开辟内存空间就好了。
情况二:原有空间之后可能被占有了,不能直接增加
如果空间之后被占有了,就如黄色方框部分,使得realloc在调整内存空间时发现没有足够的内存空间可以用。这时,realloc函数会找一块新的,足够的空间,一次性开辟足够需要的空间。
并且,找到新的空间之后:
1、realloc函数会将原来旧空间中的数据,拷贝到新的空间
2、会将旧空间释放掉
3、realloc函数会返回新的空间的地址