当前位置: 首页 > news >正文

动态内存管理

1. 为什么要有动态内存分配

2. malloc和free

3. calloc和realloc

4. 常⻅的动态内存的错误

5. 动态内存经典笔试题分析

6. 柔性数组

7. 总结C/C++中程序内存区域划分

1.为什么要有动态内存分配

我们现在已经掌握了什么开辟内存的方法呢?

int val = 20;// 在栈空间上开辟四个字节,开辟一个整型四个字节,这就开辟了内存

char arr[10] = {0};// 在栈空间上开辟 10 个字节的连续空间,内存就是连续的内存空间

但是我们虽然开辟了上面的两种内存空间,但是上面的两种内存空间有一个缺点就是他的内存空间是固定的,数组,我们在一开始的时候就要固定好他的大小,在这里的话,我们就不考虑变长数组这个问题了,因为他在应用起来的话有很多的问题,我么在这里不考虑他。

但是对于空间的需要,有时候之后再程序运行起来的时候我们才能知道他需要多么大的内存。那么这时候,我们在编译时候开辟的内存就不一定够用了。

所以今天我们新引用一个知识点,动态内存管理。在C语言中设置动态内存的管理,让程序员们可以自己自由的创建或者释放空间,这就是比较灵活的了。数组和变量不够灵活,我们自己来控制内存的声明周期。

2.malloc和free

我们先来讲解第一个函数,malloc函数,它是用来创建内存的,malloc的参数是size_t类型的无符号整型的数字,单位是字节,意思就是开辟一段长度为size字节的内存空间。他的返回值是void*类型的指针,返回的是被创建的内存块的起始的地址,但是为什么他是void*类型的指针呢?因为你也不知道你开辟的是什么类型的内存空间

看这张图片,我们开辟了一个含有40个字节的空间的内存,并且我们把它强制类型转换成立int*类型的指针。存到p指针里面。那么这40个字节,可以放10个整数,那么他就可以相当于是一个数组一样的

就像是访问数组一样,我们可以通过指针来访问这一整个数组,也是连续的,如果开辟失败的话,就会返回一个空指针NULL

free

free是专门用来做内存的回收和释放的

free的参数是指针变量,这个指针就是我们开辟的内存的地址。

但是,free这个函数他只能释放我们自己开辟的动态内存,但是有的内存,就不是我们所开辟的,就像好比是,我们要还钱,但是某一个人的前根本不是我们借的,但是你现在却要我们还,那这势必就会出现问题,我们只能换我们借的钱。。。一个意思,如果你给的参数不是我们开辟的,那这问题就大了,释放错内存。。还有一点就是,如果参数是空指针的话,意思就是欠的钱是0元,那我们就不需要还钱了,意思是一样的,这时候free也是不会有所问题的。free就不会做任何事

我们来看这张图,分析一下,我们先申请40个字节的空间,申请完了时候,你是需要先判断你是不是真的开辟到了内存,开辟这片内存成功了,使用一个if,判断它是不是一个空指针,如果他确实是一个空指针,那么就说明我们这次的开辟失败了,这时候,这个程序就没有执行下去的必要了,直接return 1;,,来结束这个主函数。但是如果我们确实成功的开辟到了内存空间,这时候p指针就是咱们开辟内存的地址,这时候咱们就可以放心地使用他,但是在我们使用结束之后,

我们首先要把我们的内存释放掉,因为我们使用系统借给我们的内存,已经完成了我们需要完成的任务,那么这块内存也就已经完成了他的使命,他现在需要回归他原本的地方供其他人使用了

但是我们在free结束之后,p指针还是一直存在的,我们释放的是内存,是指针p指向的内存空间

,但是指针p还是存在的,这时候p指向的内存被释放,那么p就是一个野指针。非常危险,所以我们要把它置为空指针。

值得注意的点就是当我们借了一本书,我们快毕业了,即使我们没有还书,图书馆也会提醒我们还书,但是,糟糕的是,当你没有毕业,你借到了这本书,却一直没还,借到的书却一直没有看完。

3. calloc和realloc

calloc这个函数也是用来开辟内存空间的,但是和,malloc有一点不同。

这个函数有两个参数。和malloc函数不同的是,malloc的参数是申请到内存空间的字节数,但是这个函数里面,前面的是元素个数,后面的是一个元素的size

函数的功能是为 num 个⼤⼩为 size 的元素开辟⼀块空间,并且把空间的每个字节初始化为0。

他的另一个作用就是将申请到的内存空间的每一个字节初始化为0;

我们通过calloc函数申请到了10个整型的内存空间,还是要先查是不是开辟成功了,if函数判断,如果他确实是空指针,我们就perror来打印calloc函数的错误信息,如果确实是空指针,申请内存空间失败了,那么这个函数也就不能再进行下去了。直接给他return 1;结束就行了。

我们继续来进行分析,我们可以看到使用了指针之后,打印的结果,10个0,这就是calloc函数,他会把开辟到的内存的字节全部初始化为0;

当然最后也不要忘记free,我们使用完内存以后要及时地释放,以便其他程序的使用。,当然,给他free之后就会变成野指针,这时候就要把p置为空指针。

malloc和calloc函数都是用来开辟内存的,那我们也使用,malloc来实现一下和calloc进行一下对比,我们可以发现,malloc函数的话,我们进行打印,结果是没有进行定义的,所以他的数字是任意的,和calloc不一样。

我们再来分析一下这个图,局部变量是存放于栈区的,全局变量和静态变量static存放于静态区,动态内存管理的存放在堆区。。

realloc

realloc的出现让动态内存的管理更加的灵活,有时候,我们会发现我们申请的内存过小了,有时会发现申请的内存过大,为了合理的使用内存,我们可以需要对内存的大小做出灵活的处理。realloc函数就可以做到对动态内存大小的调整。

这个函数不仅可以调节内存的大小,也可以向malloc和calloc函数一样,可以申请内存空间,

1. ptr 是要调整的内存地址

2. size 调整之后新⼤⼩ //要注意,这里指的是你调整完后的总的内存的大小。

3. 返回值为调整之后的内存起始位置。//返回值是调整结束之后的起始地址。

realloc函数在进行扩容的时候,有两种情况,

第一种·就是在我们的内存后面,拥有着我们需要扩增的内存空间,后面的内存空间是足够的。

第二种,我们的内存后面,没有足够的空间来供我们进行扩增,这时候

正如上图,既然这片空间不能满足我们的需要,那我们就把这个空间放弃,然后开辟一片新的空间,然后把之前的数据放到新的空间里面。

当然,在realloc函数调整失败的话,就返回空指针。

这就是realloc的使用,图中,我们先使用malloc函数开辟一片大小为20个字节的空间,但是,当我们认为这段空间大小不够的时候,我们要使用realloc进行调整,当然,在使用这个指针的时候,我们先判断内存是否开辟了出来,看他是不是空指针,然后进行调整,我们把他调整为40个字节的大小。

其实你拿realloc函数也是可以申请内存的,他也是可以像malloc函数一样可以独立的申请内存空间,

这个函数和malloc函数还是有一点相似,他的第二个参数表示的是调整完后的字节数,malloc函数只有一个参数,表示的也是申请到内存的字节数。realloc在独立的申请内存的时候,只需要他的第一个参数换成空指针NULL,调整的对象变成一个空指针,这时候他就可以独立的申请内存空间。

当然,最后也是要free申请到的内存空间。

4. 常见的动态内存的错误

1.对空指针进行解引用操作

在这个函数里面,他都没有进行判断,看他是不是空指针,如果这是空指针就错了。

2.对动态开辟空间的越界访问

3.对⾮动态开辟内存使⽤free释放

这个就出问题了,因为这块内存不是你申请来的,你却要释放,就错了,

就比如,不是你借的别人的钱,你却说要给他还钱,这就很让人摸不着头脑,这就错了。

4.使⽤free释放⼀块动态开辟内存的⼀部分

free函数是用来释放我们所申请的内存,但是它的参数是内存的起始地址,图中的这时候p已经不是内存的起始地址了。

5.对同⼀块动态内存多次释放

对一块内存释放一次就可以了,释放多次也是会出现问题的,你给图书馆还书,你当然也是只还一本,你哪里来的第二本呢?

6.动态开辟内存忘记释放(内存泄漏)

malloc和free一定是成对使用的。

我们开辟了内存之后却没有进行释放,这就会导致内存泄漏。

忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。

切记:动态开辟的空间⼀定要释放,并且正确释放。

6.柔性数组

也许你从来没有听说过柔性数组(flexiblearray)这个概念,但是它确实是存在的。 C99中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做『柔性数组』成员。

柔性数组就是结构体的最后一个成员,并且这个数组的大小是未知的。

我们看上面的图片,我们测量这个结构体的大小,但是他测量的结果居然是4个字节,这并不是联合体,他们不会共享内存,但是他只测出了成员变量n的大小,这时候就可以引出我们的柔性数组的概念了:

下面是柔性数组的特点:

我们可以看到,他说,sizeof测量包含柔性数组的结构体的时候是不会测量柔性数组的大小,他会直接舍弃掉柔性数组的大小。

2. 结构体成员中的柔性数组前面必须要有成员变量才行,不然他就不能算柔性数组。

3. 包含柔性数组成员的结构体在使用malloc申请内存的时候尽量申请的是要比结构体内存要大的,因为你还有存放柔性数组。

之前我们拿malloc函数来申请内存空间,现在我们学习了柔性数组,我们就可以使用malloc来申请一个包含柔性数组的结构体的内存空间。

我们之前说过,在创建含有柔性数组的结构体的内存空间的时候,因为这个柔性数组他的大小是不会被计入内存空间的,他的大小是未知的,因此我们在创建间的时候,创建的内存的大小是要大于结构体的大小的,因为你还要给这个柔性数组留下空间,所以,我们在创建内存的时候,我们给结构体创建一个,然后再给柔性数组创建一个,这个柔性数组的大小,就是由我们掌控的。

当然,我们创建的是一个结构体变量的内存空间,所以他的指针类型就是结构体类型,maiioc会返回开辟到的地址的起始地址,把他也给强转成struct S*类型的。

我们现在就使用这个柔性数组,我们先通过malloc函数来创建有一段结构体变量的内存空间,然后分析他是不是空指针,perror打印错误信息,return 1;返回停止函数,但是如果他对了,真的开辟出来了内存空间,我们继续进行,但我们发现我们开辟的内存空间并不够时,realloc函数来扩展,要注意他的第二个参数时变化过后的内存的大小,然后再次重复判断是不是空指针,如果是,就是用perror来打印错误信息,returen 1;返回结束。最后free我们申请的内存。并且置为空指针。

我们再来分析另一个不包含柔性数组的结构体。因为他并不包含结构体,sizeof就可以测出来他完整的内存大小,我们不需要额外的申请一段内存空间。然后就是常规的判断看他是不是空指针,perror来打印错误信息。

他首先开辟了一个结构体变量的内存空间,然后又开辟了一个整型数组的内存空间,然后把指向整型数组的指针赋给结构体变量的一个成员变量,然后对两个指针常规的判断,如果有误,perror打印错误信息,return 1;结束程序。

我们的程序后面有一个扩容realloc函数,这个函数的第一个参数是指针p,但是我们并不扩容指针p,我们,直接寻找她的根源,扩容a,因为我们扩增的是结构体的成员变量,但是我们只有结构体的指针,我们就使用->来操作。

在这里,我还要讲解一下柔性数组的优势,柔性数组相对于指针,柔性数组的优势是,在给含有柔性数组的结构体变量申请内存空间的时候,我们一次性就可以申请完毕,但是当结构体里面含有指针的时候,我们要给这个结构体申请内存空间的话,我们也要给它里面的指针变量申请内存,因为它是一个指针,这就导致了,有两个动态内存,我们free的时候就会有顺序之分,先分小的释放掉,然后再给大的内存free了,这就可能会有放错的风险。但是我们使用柔性数组就不会了。他是一次性就会把内存释放了,他只申请了一次内存空间。


http://www.mrgr.cn/news/77784.html

相关文章:

  • leetcode:112. 路径总和
  • AI+若依框架项目
  • el-tree 使用笔记
  • 【强化学习+组合优化】SAC + PointerNetwork 解决TSP问题
  • 常用数据结构详解
  • 【操作系统笔记】习题
  • 密码学11
  • 推荐一个基于协程的C++(lua)游戏服务器
  • Kubernetes的pod控制器
  • 大语言模型---什么是注意力机制?LlaMA 中注意力机制的数学定义
  • 002 MATLAB语言基础
  • 【深度学习之一】2024最新pytorch+cuda+cudnn下载安装搭建开发环境
  • 华为OD机试真题---最短木板长度
  • 【AI日记】24.11.22 学习谷歌数据分析初级课程-第2/3课
  • 模型的评估与选择——交叉验证(基于Python实现)
  • PID运动控制
  • Next.js 独立开发教程(三):CSS 样式的完整指南
  • slab分配器
  • 游戏引擎学习第20天
  • RTPS通信使用的socket和端口