一、为什么要动态内存分配
在c或c++中都会大量使用动态内存管理,同时使用C/C++实现数据结构时,也会使用动态内存管理
目前,申请内存的方式只有两种
1.1 在栈区申请内存
上面的内存申请方式,一旦申请好空间,大小无法调整
1.2 在堆区申请内存
为了引入第二种申请内存的方式,我们可以从一个问题看起“如何描述班级的学生的数学成绩”
首先我们可以想到使用数组来存储其成绩,int math[30]={0};当学生人数少于30时,空间够,但会造成浪费,当学生人数大于30时,申请的空间不够,无法达到使用需求。为了实现这种需求,引入了动态内存开辟,让程序员可以自己申请和释放空间。
二、malloc和free
2.1 malloc
函数原型如图
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针(起始地址)。
- 如果开辟成功,则返回一个指向开辟好空间的指针
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
- 返回值类型为void*,函数并不知道开辟的空间所存储的数据类型,在使用时由使用者决定
- 如果参数size为0,malloc的行为是标准是未定义,取决于编译器
如图申请了20个字节,来存储5个整数,在使用malloc前,需要包含头文件<stdlib.h>,在使用空间时,类似于数组。
通过查看内存,我们可以发现成功申请了20个字节,来存储1-5这五个整数 ,通过malloc等函数开辟的空间,均存储在堆区
在使用完开辟的内存,不再需要时,可以通过free来释放和回收内存
2.2 free
函数原型如图
free函数用来释放动态内存开辟的空间,但有几点需要注意
- 参数ptr指向的内存必须是动态开辟的并且必须是开辟空间的起始地址,如果不是,那free函数的行为是未定义的
- 如果参数ptr是NULL指针,那函数什么都不会做
- 使用free函数时,需要包含头文件<stdlib.h>
- 在使用free函数后,是把申请的内存空间还给操作系统,如果不把指针设为空指针,会使得指针成为野指针
三、calloc和realloc
3.1 calloc
函数原型如图
- 函数的功能是为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0 例如:num=5,size=4,表示开辟5个大小为4个字节的空间,即开辟大小20个字节的空间
图一为malloc开辟的空间,图二为calloc开辟的空间 通过观察可以发现,calloc开辟的空间会将每个字节初始化为0
- 与malloc的区别只在于calloc会在返回地址前,把申请的内存空间每个字节初始化为0
3.2 realloc
函数原型如图
realloc函数可以对内存的大小做灵活的调整
- ptr是要调整的内存地址,若ptr为空指针,功能与malloc等价
- size是调整后的新大小
- 返回值为调整之后的内存起始地址
realloc扩容有三种情况
情况一可直接扩容
情况二需要
1. 在堆区的内存中找到一个新的空间,并且满足大小要求
2. 把原来空间的数据拷贝一份到新的空间
3. 释放旧的空间
4. 返回新的内存空间的起始地址
情况三 若调整失败,返回NULL指针
四、常见的动态内存的错误
4.1 对NULL指针的解引用操作
4.2 对动态开辟空间的越界访问
4.3 对非动态开辟内存进行释放
4.4 使用free释放一块动态开辟内存的一部分
4.5 对同一块动态内存多次释放
4.6 动态开辟内存忘记释放(内存泄露)
五、柔性数组
C99中,在结构体中,最后一个成员是数组,且数组没有指定大小,这个数组才是柔性数组
5.1 柔性数组的特点
- 结构体中的柔性数组成员前面必须至少有一个其他成员
- sizeof返回的这种结构体大小不包括柔性数组的内存
- 包含柔性数组成员的结构体用malloc函数进行内存的动态分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的预期大小