目录
[1. 为什么要有动态内存分配](#1. 为什么要有动态内存分配)
[5. 常⻅的动态内存的错误](#5. 常⻅的动态内存的错误)
注:使用这些函数要添加stdlib.h头文件
1. 为什么要有动态内存分配
我们已经知道开辟内存空间的方式大致有:

但是,上面我们说的这两种方式有个特点:
- 空间开辟⼤⼩是固定的。
2.数组在申明的时候,必须指定数组的⻓度,数组空间⼀旦确定了⼤⼩不能调整
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间⼤⼩在程序运⾏的时候才能知
道,那数组的编译时开辟空间的⽅式就不能满⾜了。
所以,C语言引入了动态内存开辟,让程序员可以自己开辟空间和释放空间,使其更加灵活。
**动态开辟的内存是放在堆中进行管理的,**说到这里,我们就得大致了解一下C/C++程序内存区域的划分。
1.1C/C++程序内存区域划分
我们已经知道局部变量出了作用域就会被销毁,而全局变量的作用域使整个工程,还有static修饰局部变量会 ,该变量的生命周期会延长至整个程序运行期间。这是为什么呢?这就涉及到了 C/C++程序内存区域划分:
局部变量和全局变量生命周期和作用域不同,是因为它俩在内存中存储的位置不同;static修饰局部变量引起的改变是因为改变了该局部变量在内存块中的存储位置。
2.malloc
void*malloc(size_t size);
这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
注意事项:
1.如果开辟成功,则会返回一个指向开辟空间的指针。
2.如果开辟失败,则会返回NULL指针,所以malloc函数的返回值一定要检查!!!
3.size的大小不能为0,否则引发未定义的行为
4.返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使⽤的时候使⽤者⾃
⼰来决定。
3.free
C语言中专门用来回收和释放动态内存的函数,函数原型如下:
void free (void* ptr);
注意:
1.如果参数ptr指向的空间不是动态开辟的,那么free函数的行为是未定义的
2.如果传入NULL指针,则free函数什么都不做。
使用案列:

这段代码我们利用malloc函数开辟了5个整形大小的内存空间,并且进行初始化,把数值打印了出来,最后利用free函数释放内存。
这里有人可能会问:

这里p指针指向的空间都已经释放掉了,还有必要将p赋为空指针吗?
答案是非常有必要
free(p)这一步仅仅是将p指向的动态开辟的内存的操作权限还给操作系统,
但是p此时仍然指向该内存区域,此时又没有这块内存的使用权限
此时,不把p置为NULL的话,p就是野指针
所以,free函数使用的标准步骤就是上面两步。
4.calloc和realloc
4.1calloc
calloc函数原型如下:
void*calloc(size_t num,size_t size);
函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。
该函数与malloc函数唯一区别就是calloc函数会在返回前把申请的空间的每个字节都初始化为0
举个例子看一下:

所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务。
4.2realloc
realloc函数原型如下:
void*realloc(void*ptr,size_t num);
该函数的功能是,调整ptr指针指向的内存空间的大小,将其大小改为num个字节。
该函数使得动态内存管理更加灵活
注意:
1.ptr是要调整内存的地址,num是调整后内存的大小。
2.返回值为调整后内存的起始位置。
我们来看个例子:

上面这段代码我们先用malloc函数动态开辟了20个字节的空间,然后对其进行初始化,打印;
之后运用realloc函数扩容成40个字节,并进行初始化,最后释放内存。
代码还是比较容易理解的,那么笔者有个问题,这里:
能不能写成这样:

万万不可啊!
万一开辟失败了,realloc会返回NULL,这时候你拿p接收,不仅没达成目的,反而把p给干成空指针了,这时候你会发现malloc开辟的空间找不到了,这么写代码是要被开除的哈哈哈。
所以使用realloc函数时一定要注意这一点,即先用一个临时指针(ptr)接收返回值,然后判断临时指针(ptr)是不是空,在传给p.
5. 常⻅的动态内存的错误
5.1未对函数返回值检验,导致对空指针进行解引用

这段代码就是一个典型的错误案例,malloc函数的返回值用指针p接收,但有可能malloc函数开辟失败返回NULL,那么p可能为NULL,后面对p解引用可能会对NULL指针解引用,故会引发错误.
所以,动态内存开辟时一定要对相关函数的返回值进行检验!!!
5.2对非动态开辟内存使用free释放

比如这段代码,对非动态开辟的内存使用free释放。这种也是一种比较典型的错误,是属于学的走火入魔了,看到内存就想着free^_^,这个错误我们要注意一下
5.3使用free释放⼀块动态开辟内存的⼀部分

这段代码乍一看好像没有什么问题,但我们仔细看上一看,就会发现:

循环内部改变了p指针的指向,所以后面进行free释放内存时**,只是释放了一部分内存,而没有完全释放动态开辟的内存。**
这个错误比较容易被忽略,我们要多加注意一下。
5.4动态内存忘记释放
这个错误对于初学者来说很容易犯,动态内存忘记释放。动态内存忘记释放有那些危害呢?
内存泄漏可能会导致以下问题:
-
程序性能下降:随着时间的推移,内存泄漏累积的内存占用会逐渐增加,导致程序运行速度变慢,甚至耗尽系统内存资源,导致系统崩溃或进程异常终止。
-
系统稳定性降低:内存泄漏会导致系统的可用内存不断减少,可能会影响到其他正在运行的程序,甚至导致系统性能下降或系统崩溃。
-
资源耗尽:如果内存泄漏发生在长时间运行的服务或后台程序中,可能会消耗大量的系统资源,如内存、CPU 资源等,最终影响系统的稳定性和可用性。
-
难以排查的 Bug:内存泄漏可能会导致程序出现难以复现或难以定位的 Bug,因为内存泄漏通常不会立即显现出问题,而是随着时间的推移才会引发程序运行异常或崩溃。
危害还是很大的,所以我们一定要释放动态开辟的内存。
6.动态内存经典笔试题解析
经过上面的学习,我们已经对动态内存管理有了一个大概的了解,但**实践是检验真理的唯一标准,**接下来我们来看看一些经典的笔试题
题目一:

这段代码的运行结果是什么呢?会打印hello world吗?
答案:程序会崩溃。
原因如下:
GetMemory(str)传参是**传值调用(这个我们在指针中讲过),用形参p接收,**此时,p和str都是空指针,但是两者的地址不一样,如下图所示:

经过前面的学习,我们也知道,此时改变形参p不会影响到实参str
后面malloc开辟空间后将开辟空间的地址返回给了p此时p的内容不在为空,而是动态开辟内存的地址,就比如下图:

然后退出函数GetMemory,因为p是形参,所以出函数会被销毁,不能再被使用,那么这时候问题来了:开辟的空间找不到了
同时,因为str为NULL,strcpy对str进行了解引用,所以对空指针解引用又是一个错误。
综上所述,这段代码有两个错误:
1.内存泄漏
2.对NULL解引用
那么应该怎么修改呢,只要改成传址调用即可,代码如下:

题目二:

这段代码运行结果又是如何呢?
答案:打印结果不确定,可能是hello world
原因:

此时,即使str指向该数组,但是此时数组中的内容已经不知道是什么了,所以打印结果是随机值。
题目三:

这段代码不就是我们第一题改造后的代码吗?那这没问题吧
仔细看我们会发现,这段代码犯了我们上面讲的常见错误中的动态内存忘记释放。