| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> C++知识库 -> “史上最全”C语言动态内存及使用动态内存经常出现的错误 -> 正文阅读 |
|
[C++知识库]“史上最全”C语言动态内存及使用动态内存经常出现的错误 |
? ? 一、为什么会有动态内存?因为当前的内存分配有一定的局限性,例如使用数组只能开辟固定的空间而不能动态开辟,而动态内存能让我们想要多大空间就要多大空间。 二丶动态内存如何使用?了解并且会使用动态内存的函数,比如malloc函数,calloc函数,和realloc函数。 首先先讲解malloc函数,以下是malloc函数的返回值和函数参数 malloc的返回值为void*类型,函数参数为size_t size?这个参数实际上是你要告诉malloc函数你要开辟多大的空间,比如malloc(40)就是开辟40个字节的空间,malloc引用的头文件是stdlib.h或者malloc.h。下面讲解该如何使用malloc函数,如图所示: 首先你要确定你要开辟什么类型的空间,如果你用char*指针开辟那么就要将malloc强制转化为char*类型,如果你要用int*指针开辟那么就要将malloc强制转化为int*类型,刚刚忘记说了,malloc函数开辟空间并不是开辟就能成功,而是有一定几率开辟失败,一旦开辟了就会返回空指针,所以我们在使用malloc函数前一定要判断是否开辟成功,如果开辟成功我们就使用,如果开辟失败我们就使用perror函数打印错误信息,perror的使用方法如图所示,你可以直接使用里面不加提示信息,加提示信息malloc是为了告诉他人这个错误是因为malloc开辟失败报错的。最简单的使用方法?就是将你要存储的数据放入malloc开辟的空间中,在这里一定要切记不能越界,比如下图所示: 你只开辟了10个整形的空间但是你使用的时候多用了10个整形的空间,这就会造成越界。在C语言中所有堆空间使用完后都要手动释放内存,如果不释放内存就会造成内存泄漏,释放完后再将指针置为空指针,这样就可以避免野指针的出现。既然说到了释放那么我们一定要注意释放一定是从开辟空间的起始位置开始释放的,切记如果你再使用的时候让开辟空间的指针加加或减减这时候指针已经不在初始位置了,如果这时候释放内存就会造成内存泄漏。 讲解完malloc我们再讲解一下calloc函数,calloc函数和malloc唯一的区别就是calloc函数会将开辟的空间初始化为0。calloc函数返回值及参数如下图所示: 如图可见calloc函数的返回值也为void*只不过参数变成了size_t num和size_t size,?size_t num这个参数是你要开辟多少个相同类型的数,size_t size这个参数是你要开辟的单个元素的类型,比如你要开辟10个int类型的空间就应该写成calloc(10,sizeof(int))。以下是使用calloc函数方法: calloc函数malloc相同开辟失败都会返回空指针所以都先需要判断是否开辟成功,只不过calloc会将开辟的空间初始化为0如图所示: 地址不同是因为每次重新调试程序地址都会重新分配至于为什么不是直接从指针地址处直接使用空间是因为编译器在堆区会留一部分位置为空然后再开始使用空间。calloc函数讲解完后就是realloc函数,realloc函数可以在你之前开辟的空间用满了然后再原先空间的基础上在开辟空间,当然也可以是因为你开辟的空间太大然后在原先开辟空间的基础上减少空间。以下是realloc函数的使用方法,如下图所示: 可以看到realloc函数的返回值也是void*类型,函数的参数为一个指针和要开辟空间的大小,在下面参数解释中可看到指针是要增加开辟空间或减少开辟空间的位置,并且realloc也有开辟失败的情况,开辟失败同样返回空指针,以下是realloc的使用方法: 我们可以看到我在接受realloc开辟的空间的时候用了另一个指针ptr,这是因为realloc有可能开辟失败,一旦开辟失败realloc会返回空指针,如果用一开始的ps指针接收,那么这个时候ps被赋值空指针你就再也找不到ps的地址,同时由于之前ps的空间使用了没有释放所以就会造成内存泄漏,而用另一个指针接收就会避免这样的问题,只需要在开辟成功的时候将ptr的地址给ps指针然后将ptr指针置为空指针,以下是成功使用开辟空间的图: 在这里要说明reallo开辟空间分为两种情况: 1.第一种情况是原先空间的后面有足够大的空间开辟空间,那么就在原来空间的后面直接开辟空间。 2.第二种情况是原先空间的后面没有足够大的空间开辟空间,那么就需要在内存中重新找一个空间足够大的地址?来开辟,开辟好后将原先空间的内容拷贝到新的空间,然后再释放原先的空间。 当然,realloc函数既然能重新找空间开辟那么是否可以像malloc函数那样直接开辟空间呢?答案是可以的,只需要将realloc的起始地址传为空指针,那么就实现了malloc的功能,如下图所示: 以上就是内存函数的讲解及使用了,下面我们来讲解如何用这些函数实现一个?动态开辟内存的通讯录,由于动态版本与基础版本大部分代码相同,所以我只讲解动态版本与基础版本不同的地方。 首先,我们定义通讯录不能使用数组的方式而是用指针,因为指针才可以使用动态开辟内存函数,如图所示: 我们新增的capcity是为了记录空间是否满了需要再开辟空间,当sz==capcity的时候说明空间不够就要再开辟空间了,同时既然没了数组那么初始化通讯录也随之改变,这次初始化通讯录则直接用malloc函数开辟空间,这样才能在后续通讯录慢需要开辟空间的时候用realloc函数增加空间,如图所示: ?由于考虑使用指针的安全性所以我用了assert断言,这样当指针为空时就会报错,我们在创建通讯录的时候将数组改为了struct pepinfor*的指针,那么现在自然是用data指针去接收malloc开辟的空间,为了以后修改刚开始有多少空间和后续空间不够要自动开辟的空间个数,所以我定义了两个变量,BASIC_SIZE是控制这个通讯录最开始有多大空间,BASIC_NUM是控制realloc在空间不足时要再开辟多大空间,定义如下图所示: ?前面我们说过,malloc,calloc以及realloc使用前要先判断是否开辟成功也就是返回的是不是空指针,如果是空指针则说明空间开辟失败了这种情况了返回空即可,如果开辟成功了那么我们就该初始化sz和capcity了,由于我们期望的是通讯录刚开始要有三个人大小的空间,所以我们将capcity赋值为3,sz为0指向开辟的第一个空间。初始化后我们就需要修改添加功能了,因为之前用数组开辟的条件是sz不等于数组最大空间我们就添加,现在是如果capcity等于sz说明空间满了需要开辟空间,如果sz小于capcity说明空间足够直接添加即可,按照这样的思路我们写出以下代码,如图所示: ? 同样我们为了保证使用指针的安全性先断言指针,在这里本来直接判断是否需要开辟内存即可,但是我将开辟内存的功能封装为函数,原因是如果直接判断那么当空间开辟失败的时候还会跳到下一个语句让你输入姓名,很明显这是错误的我只有开辟成功才会输入姓名,所以我们通过函数返回值的方式来避免这个问题,在realloc开辟空间需要注意的问题是,我们开辟空间是用data指针的,data指针的类型是struct pepinfor*,所以我们要将realloc强制转化为struct pepinfor*,同时接收指针我们用相同类型的ptr指针,这里为什么用另一个指针接收之前像大家提到过,realloc开辟失败会返回空指针,如果直接用data指针接收一旦开辟失败那么之前malloc开辟的空间地址被制为空就会造成内存泄露,在判断空指针我们可以看到如果开辟失败我直接用perror报错然后结束程序,这样就避免了开辟失败还要进行下面功能的情况。开辟成功后我将ptr指针赋给data指针然后将ptr指针置为空,并且容量capcity也随之改变我们让每次开辟一次内存容量就加上开辟内存的个数,返回值是如果空间满了开辟空间后返回1开始录入信息,同样如果没满那就有空间直接返回1录入信息即可,在添加函数中只需要判断函数返回值是否为1为1就录入,为0就返回空。改完添加功能我们再思考其他哪个功能还需要更改,我们发现删除不用,搜索不用,修改不用,排序不用,只需要在程序退出的时候开辟的空间释放即可,以下是声明及代码实现: ?我们只需要将data指针开辟的空间释放并且置为空指针,让记录添加了多少个人的变量sz置为0,将capcity也置为0即可,这就是通讯录的动态开辟版本,后续还有动态开辟+文件保存版本。 本次的重点是三个内存函数的使用,并且知道使用完开辟空间后要释放空间并且将指针置为空指针避免野指针的出现。 下面讲解在使用内存开辟函数过程中经常出现的错误: 1.对空指针进行解引用操作。如图所示: 之前我们已经说过,malloc开辟失败会返回空指针,那么使用该内存的时候应该先判断是否为空指针,如果不判断直接使用就会有可能对空指针解引用使用空间。以下是改进代码:
2.第二种错误:对动态开辟内存空间进行了越界访问。如图所示:
一共用malloc函数开辟了10个整形的空间,但是在使用的时候用了20个int类型空间,多用的10个空间就造成了越界访问,防止越界访问?最重要的就是细心。 3.第三种错误:对非动态开辟内存使用free释放。如图所示: 我们之前已经说过,free函数释放的是动态内存开辟的空间,这些空间是在堆上开辟的如果不释放就会一直占用内存,而图中a变量是在栈上开辟的空间,程序结束自动销毁或者在函数内部用完即销毁,是不需要释放的,当然我用的vs2019编译器没有报错,但是这种行为本身是无意义的。 4.第四种错误:使用free释放动态开辟内存的一部分。如图所示:
使用free释放空间是从哪开辟的从哪释放,而图中代码很明显p++进行自加后位置已经变了,这时候释放空间就不是刚刚开辟的空间了。 5.第五种错误:对同一块动态开辟内存进行多次释放。
6.第六种错误:动态开辟的内存空间忘记释放(内存泄漏)如图:
以上就是在动态开辟内存过程中经常出现的错误及如何预防错误,下面来讲解关于动态开辟内存的几道经典笔试题: ?第一道如下图: 看到此题你是否认为可以正确打印hello?world。答案是并不会打印hello?world,首先进入test函数,test函数里有一个str的空指针,然后getmemory函数调用了str?指针,在getmemory里p是str指针的一份临时拷贝,然后给p开辟了100字节大小的空间,然后回到test()函数strcpy将hello?world拷贝到str指针里,在这里就出现了问题,如图所示: 空间是p开辟的str指针里还是空指针所以根本不能拷贝出helloworld的信息,并且开辟空间后并没有进行释放,同时malloc函数会有可能开辟失败我们一定要先判断是否开辟成功,以下是修改后的代码:
为什么传地址后就对了呢?首先,getmemory传的str的地址,然后getmemory函数用二级指针接收str的地址,malloc开辟的空间用*p接收,为什么是*p呢?首先p是二级指针,解引用后就找到str指针所以实际上空间开辟给了str所以拷贝成功。下面是第二种方法: 同样getmemory函数用str的临时拷贝p开辟了100个字节的空间,?我们将getmemory函数的返回值改为char*类型,当开辟成功后直接返回p的地址,返回后用str指针接收刚刚p开辟的空间,如图所示: ?这样就成功打印出hello?world。 下面是第二道笔试题: 看到此代码是否可以成功打印hello?world?答案是不可以,原因进入test函数以后,创建了一个str指针,用str指针接收getmemory的返回值,而进入getmemory函数以后,有一个char类型数组p存放字符串hello?world,然后返回p的地址,p的地址实际上是字符串首字母h的地址,此代码错误的地方在于p数组在栈上开辟的空间,getmemory函数结束后就已经销毁p数组的空间,所以打印str会造成越界访问,str访问了已经销毁的空间。 第三道笔试题:
那么看到此代码你会感觉有什么问题?我们从进入test函数开始,首先在test函数内部创建了个char*的?指针str,然后getmemory函数将str的地址和100传入,在getmemory函数中用二级指针p接受了str的地址,用num接收了100,然后malloc开辟的空间用*p接收,*p是str也就是空间开辟给了str指针,回到test函数,将hello拷贝给str指针,现在str指针里就是hello字符串首元素h的地址,打印时直接打印hello,那么这个代码有什么问题呢?第一个问题,malloc开辟空间需要先判断是否为空指针,否则有可能造成对空指针解引用。第二个问题,malloc开辟的空间要进行释放。修改后的代码如图所示: 第四道笔试题:
首先进入test函数,用str指针接收malloc开辟的100字节的空间,然后直接将hello字符串的首元素地址传给str,紧接着释放了str的空间,这时候错误就来了,str的空间已经释放了,下面if语句中又说str不等于空指针的时候将world拷贝给str,而且我们在使用空间前就要判断是否为空指针。修改代码如下:
? ?以上就是C语言中动态内存的开辟以及动态内存中容易出现的错误。 ? ?? ? |
|
C++知识库 最新文章 |
【C++】友元、嵌套类、异常、RTTI、类型转换 |
通讯录的思路与实现(C语言) |
C++PrimerPlus 第七章 函数-C++的编程模块( |
Problem C: 算法9-9~9-12:平衡二叉树的基本 |
MSVC C++ UTF-8编程 |
C++进阶 多态原理 |
简单string类c++实现 |
我的年度总结 |
【C语言】以深厚地基筑伟岸高楼-基础篇(六 |
c语言常见错误合集 |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年3日历 | -2025/3/4 11:32:37- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |