C语言·动态内存管理

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

        当我们用之前的办法创建一个变量或者数组的时候,当时创建了多大的空间,后来就只能用多大的空间。但是很多情况下对于空间的需求不一定是固定的,有时我们需要的空间大小在程序运行的时候才能知道,那数组在编译时开辟的空间大小就不一定满足需要用到的空间大小了。因此C语言引入了动态内存开辟,让程序员自己可以申请和释放空间,这样就比较灵活了

2. malloc 和 free

2.1 malloc

                void* malloc (size_t size);

        malloc函数用于在堆区中开辟一块连续可用的空间,并返回指向这块空间的指针。参数size的含义是指开辟这块空间的大小

        官网资料:malloc - C++ Reference (cplusplus.com)

        如果开辟成功,则返回一个指向开辟好空间的指针

        如果开辟失败,则返回一个空指针,因此malloc函数的返回值一定要检查

        返回值的类型是 void* 所以在使用的时候要指定malloc函数开辟的空间的类型

        如果参数size的值是0,malloc函数的行为是C语言标准中未定义的,具体行为取决于编译器

2.2 free

                        void free (void* ptr);

        free函数是专门用来释放动态内存的,其参数ptr是指向动态内存的指针,注意是动态内存

        官网资料:free - C++ Reference

        如果参数ptr指向的空间不是动态开辟的,那free的行为就是标准中未定义的//具体行为取决于编译器

        如果参数ptr是空指针,则函数什么事都不做

        使用这些动态内存操作函数都要引用头文件stdlib.h

2.3 实战

        下面我们尝试使用一下这两个函数

        这段代码中展示了用malloc函数创建动态内存实现类似数组效果的功能。这里有3点值得注意,第一,molloc函数在使用的时候将其返回值强制转换成了int*型,这样再赋值给p,否则void*类型是不能进行加减操作的。第二,我检验了一下p是否是空指针,如果是空指针说明空间开辟失败了,然后将失败的错误码信息打印出来。第三,再使用完这块动态内存后要记得free释放,同时将指针变量p的值设为空,以便下次使用。

        可能你会好奇如果malloc空间创建真的失败了会怎样,下面我们尝试一下

        这段代码我让malloc创建一块巨大无比的空间,于是它创建失败了,错误码打印出来的信息是Not enough space,然后程序return,返回了一个代码1

3. calloc 和 realloc

3.1 calloc

                        void* calloc (size_t num, size_t size);

        calloc的作用和malloc一样,都是开辟一块动态内存,参数num是指要开辟几块连续的空间,size是指每块空间的大小是多少,其返回值是这一整块空间的起始地址

        官网资料:calloc - C++ Reference

        与malloc和realloc不同的是calloc会将开辟的内容都初始化成0,而那两个函数并不会初始化其开辟空间的内容

        我们尝试用calloc开辟一块空间

           

3.2 realloc

        realloc函数的出现是真正的让动态内存管理更加的灵活,前两个函数其实还是开辟了一块定死大小的空间,而realloc不一样,它可以随意改变设定好的那块空间的大小,但是要注意只能改变堆区中动态内存的大小,像栈区中的那些变量是不能操作的

                        void* realloc (void* ptr, size_t size);

        参数ptr是要调整的动态内存地址,size是从新调整后的大小,返回值一般是调整后的内存起始位置

        官网资料:realloc - C++ Reference

        为什么说是一般呢,事实上realloc的返回值分3种情况

        情况1:空间调整失败,返回空指针NULL

        情况2:要调整的空间后面有足够的空间可以扩容,那么realloc会直接扩容这块空间,并返回这块空间的地址

        情况3:要调整的空间后面有其他已经被使用了的空间,没有足够的空间可以扩容,那么realloc会再在堆区找一块空间开辟相应的大小,并将原空间中的内容全部复制到新空间中去,然后释放原来的空间,最后返回新的空间的地址

        基于三种情况我们在接收realloc的返回值的时候要小心

               

        这段代码中我创建了一个指针变量ptr用于临时接收realloc的返回值,这样做的意义是防止realloc创建空间失败,返回了一个NULL,如果此时用p去接收NULL的话,那么malloc创建的那块空间都找不到了,也永远无法释放了,这种情况被称作内存泄露的问题

        如果ptr不是空指针,就将它赋值给p然后继续用,如果是空指针就打印错误信息

        事实上realloc还可以当作malloc用

                 

        就像这样,当realloc的第一个参数是空指针NULL的时候realloc就会在堆区随机找一块空间开辟,这与malloc的实现逻辑是完全一样的

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

4.1 对NULL指针的解引用操作

        如果不判断p是不是空指针就盲目的对它进行操作,这是错误的操作

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

        越界访问之后程序会直接崩溃,并回馈堆区(HEAP)越界访问的信息

4.3 对非动态开辟的内存空间使用free释放

        这段代码中我将malloc创建的动态内存地址赋值给p之后又写了好多代码,但是在这其中我写昏头了,将a的地址又赋值给了p,最后用free去操作p,这样就让free尝试释放了一个非动态开辟的内存空间,最终结果就是程序崩溃报错

        在这里p的内容被篡改了之后malloc创建的那块空间就像之前那段代码一样,又内存泄露了,这里我想补充一点,malloc calloc realloc申请的空间,如果不主动释放,即使出了使用它们的作用域也不会销毁的,它们申请的空间的释放方式只有两种

        1.free主动释放

        2.直到程序运行结束,才由操作系统回收。但是像有些服务器,它的程序要一直运行着,那么这些内存就真的永久泄露了

4.4 使用free释放一块动态开辟内存的一部分

        这段代码是说我在后续写代码的途中不小心将p的值增大了1,这样它就指向了动态开辟内存的一部分,但是这样运行的话就会崩溃报错,因为free中的参数必须指向整块动态内存

        所以说我们在使用动态内存的时候尽量不要改变指向它起始位置的指针的大小

4.5 对同一块动态内存多次释放

        这段代码是说我在后续写的时候我已经将p指向的动态内存释放了,但是我给忘了,于是又释放了一次,这样的话程序也会崩溃报错

        当然这也跟书写习惯有很大关系,如果我在第一次释放的时候就把p设为NULL了那第二次释放的时候参数就是个空指针,free什么都不会做的,程序也会照常执行下去

4.6 动态开辟内存忘记释放

        动态开辟的内存没有释放的情况我们之前已经看了好几例了,这段代码展示一下即使我没有忘记写释放动态内存,但是因为test函数提前返回了(a > 0),导致的内存泄露问题,这里我在主程序中设置了让它一直它运行的死循环,因此随着内存被逐渐吞噬,这个程序最后还是会崩溃的

        所以说我们在写代码的时候一定要头脑清晰,不要出现内存泄露的问题

5. 柔性数组

        在C99中,结构体的最后一个元素允许是未知大小的数组,这就叫做 柔性数组 成员。

        下面我们创建一个柔性数组试试

                     ​​​​​​​        ​​​​​​​        

        可能有些编译器不认这么写,那就试试把数组中间写个0,像这样 arr[0]

5.1 柔性数组特点

        结构体中柔性数组前面必须至少有一个成员

        用sizeof计算结构体大小时不包括柔性数组的内存大小

        包含柔性数组成员的结构体用malloc函数进行动态内存分配,并且分配的内存应该大于结构体的大小,以适应柔性数组的大小

5.2 柔性数组的使用

        下面我们尝试使用一个柔性数组

        当然,如果突然发现这个柔性数组的大小不够用了,我们还可以使用realloc来拓展它

        在这里我还要分享另一种创建结构体的方案,以实现类似柔性数组的功能

    

        最后在释放空间的时候是有讲究的,要先把arr指向的空间释放掉,在释放p,否则就找不到arr指向的那块空间了

        这两种方法各有利弊,第一种方柔性数组的方案方便内存的释放,一次free就可以解决问题。第二种方案的好处在于有利于访问速度,利于减少内存碎片

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

        ​​​​​​​        ​​​​​​​          

        1.栈区(stack):函数内部的局部变量在这里创建,函数执行结束之后这些储存单元会自动释放

        2.堆区(heap):一般由程序员释放,如果程序员不释放,程序结束的时候由OS(操作系统)回收

        3.数据段(静态区)(static):存放全局变量、静态数据。程序结束后由系统释放

        4.代码段:存放函数体、类成员函数和全局函数的二进制代码

  • 14
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值