前言
平时我们在写代码的时候就常常要遇到这样的问题,有段空间的大小是在程序运行的时候才知道其具体的大小。而为了数据的完整往往需要开辟一块巨大的空间供程序使用。这时候,我们便想说有没有一种方法可以恰恰好开辟一个我们需要的空间,不会浪费内存的空间,也能保证程序的完整运行。于是,动态内存便孕育而生。他与我们平时创建的局部变量不同,局部变量是创建在内存的栈区之中,离开了其作用域,这个变量所占的空间就会被内存所回收,而动态内存则创建在内存的堆区,他的作用域就是整个程序,若无外界因素而导致这块动态内存被释放,则直到整个程序结束,这块开辟的内存空间则会一直存在。同时其向内存申请的大小可以根据需要随时调整。
malloc
事不宜迟,这就介绍动态内存的第一个函数。
通过查找资料我们得知, malloc 函数需要传参一个单位是字节、数据类型是size_t的数据,即申请一片大小为 size 字节的空间后由于系统并不知道这片空间是用来管理什么数据,所以他返回void*指针(方便使用者转换)。这是开辟成功的情况,若系统内存不足以开辟如此大的空间,则 malloc 函数便会返回一个空指针,正因如此对于 malloc 的返回值我们必须进行检查,若是对空指针进行解引用操作便会产生错误。同时 malloc 函数的头文件是 <stdlib.h> 所以得先引用其头文件之后才能正常使用这个函数。
如下所示,我们使用 malloc 申请了 40 个字节的空间并用转换成整型指针进行管理。不禁就想看看这个函数开辟的空间究竟是怎么样的。
进入调试后,找到 ptr 所指向的空间,我们便可以看到向内存申请的 40 个字节的空间,这时候我们便注意到这些空间里面都存放着 cd 即 malloc 不会对申请的这片空间进行初始化,若要初始化这篇空间,只需要使用memset或是用循环遍历一遍就能完成。
calloc
这个函数跟 malloc 相似一样是在内存之中开辟动态内存,与之不同的是 calloc 所要传的参数比 malloc 多一个,即开辟 num 个 size 大小的空间供程序员使用,同时,若开辟成功则返回开辟空间的 void* 类型的首地址,否则返回空指针,因此也需要对其返回值进行检查。
同样,我们用 calloc 开辟十个 int 类型大小的空间(一样是40),并观察整个开辟的空间。我们可以看到,空间内存中都是0,即calloc对所开辟的空间会将其初始化成0,避免发生意外的错误,规避风险。
free
我们知道,动态内存开辟出来的空间的作用域非常地宽广,为了避免内存浪费而造成内存的泄露,保证程序运行的速度始终在线。正所谓:“有借有还,再借不难。”所以通过 free 函数对开辟后不再使用的空间进行回收和释放。
free函数接收一个指针,将其释放并没有返回值。值得一提的是所传输的地址必须是申请空间的首地址,这样才能正常运行,因此建议不对原指针进行直接运算,而是用间接的方法来访问整个空间。同时,一个 malloc 或 calloc 对应一个 free ,保证不使用就释放。
这时候我们发现,ptr 还是存有某个位置的地址,但那个地址我们并没有使用的权力,即为野指针,若对其解引用操作就会导致错误的产生。所以我们还需要对这个指针进行制空的处理。
这样就可以保证 ptr 接下来不会对程序造成影响。这样处理过后才真正地完成动态内存的善后工作,一定要做到有申请就有释放,保证程序的正常运行而不会造成内存泄漏。
值得提一嘴的是,如果让 free 去释放一个定义的变量,那结果将取决于编译器,不同的编译器会有不同的结果。若释放一个空指针,free 则什么事都不会做。
realloc
讲到这里,可能有人想问了,不是动态内存吗?动呢?,前面那些定义下来好像也不会动吧。
别着急,这不就来了吗。
realloc 这个函数就负责动态内存的动,可以对动态内存的空间的大小进行修改。需要输入一个动态内存的地址,后面的参数为修改后这片空间所占的单位是字节的大小(切记不是增加或减小的大小)。并且返回一个void*指针或空指针所以也必须对返回值检查,若创建失败没有检查就将原指针置换成空指针,就连原来空间的使用权也丧失了,如此便得不偿失了。
如此我们将ptr所指向的空间大小增大到80,从上图我们是可以看出来realloc是也是不会对开辟的空间进行初始化。
值得一提的是,在 realloc 开辟空间时会出现两种情况,第一种,当原来开辟空间的后面足够我们需要的,便会在原空间之后继续申请空间使用。
若原空间之后没有足够的空间让新申请的空间使用,则会选择一块新的足够大的空间供程序员使用,同时将原地址释放。
从上图便可以看到前后两个地址是不同的,也印证了前面说的系统会寻找一个全新的空间来代替原来的空间。
于是从定义上我们就可以看出,realloc(NULL,40) 与 malloc(40) 二者是等价的。
常见错误
前面说过,一个申请对应一个释放,但只是如此就能避免数据泄露吗?
如上代码,我们确实做到了一申请一释放,但实际上的结果好像并不如我们所愿。函数进入一个选择语句后就返回了,而p却没有被释放并制空。所以不仅仅是形式上要有一申请一释放而且逻辑上也要做到一申请一释放。
这样,关于动态内存的介绍就到此为止了,祝愿每位学习C语言的同学们都能凭自己努力而取得蜕变。