《C语言深度解剖》(20):动态内存管理中的易错点和避坑指南

🤡博客主页:醉竺

🥰本文专栏:《C语言深度解剖》

😻欢迎关注:感谢大家的点赞评论+关注,祝您学有所成!


✨✨💜💛想要学习更多C语言深度解剖点击专栏链接查看💛💜✨✨ 


        本篇文章将学习动态内存管理的诸多易错点和避坑指南,学习完本篇文章,你会对内存管理的理解更加透彻,你会了解内存管理中常见的和隐蔽性很强的错误,从而汲取开发经验!对动态内存管理还不是很熟悉的可以先看下面一篇文章~,下面一篇文章中讲解了内存管理的一些库函数的使用,以及一些常见的错误!

动态内存管理icon-default.png?t=N7T8https://blog.csdn.net/weixin_43382136/article/details/138870524?spm=1001.2014.3001.5501


目录

1. 什么是野指针? 

2. 栈、堆和静态区 

3. 常见的内存错误及对策 

3.1 指针没有指向一块合法的内存 

3.1.1 结构体成员指针未初始化 

3.1.2 没有为结构体指针分配足够的内存 

3.1.3 函数的入口校验 

3.2  为指针分配的内存太小

 3.3 内存分配成功,但并未初始化

3.4 内存越界 

3.5 内存泄漏 

3.5.1 告老还乡求田 

3.5.2 如何使用 malloc 函数 

3.5.3 用 malloc 申请 0 字节内存

3.5.4 内存释放 

3.5.5 内存释放之后 

3.5.6 内存已经被释放了,但是继续通过指针来使用 


1. 什么是野指针? 

        那到底什么是野指针呢?怎么去理解这个“野”呢?我们先看别的两个关于“野”的词:

        野孩子:没人要,没人管的孩子;行为动作不守规矩,调皮捣蛋的孩子。

        野狗:没有主人的狗,没有链子锁着的狗,喜欢四处咬人。
        对付野孩子的最好办法是给他定一套规矩,好好管教,一旦发现没有按规矩办事就好好收拾他。对付野狗最好的办法就是拿条狗链锁着它,不让它四处乱跑。
        对付野指针恐怕比对付野孩子或野狗更困难。我们需要把对付野孩子和野狗的办法都用上。既需要规矩,也需要链子。

        前面我们把内存比作尺子,很轻松地理解了内存。尺子上的0 mm 处就是内存的 0 地址处,也就是 NULL 地址处。这条栓“野指针”的链子就是这个“NULL”。定义指针变量的同时最好初始化为NULL,用完指针之后也将指针变量的值设置为NULL。也就是说除了在使用时,别的时间都把指针“栓”到 0 地址处,这样它就老实了。

2. 栈、堆和静态区 

        对于程序员,一般来说,我们可以简单地理解为内存分为 3 个部分:堆、栈和静态区。

        很多书没有把堆和栈解释清楚,导致初学者总是分不清楚。其实堆栈就是栈,而不是堆。堆的英文是 heap;栈的英文是 stack,也翻译为堆栈。堆和栈都有自已的特性,这里先不做讨论。

        再打个比方:一层教学楼,可能有外语教室,允许外语系学生和老师进人;还可能有数学教师,允许数学系学生和老师进人;还可能有校长办公室,允许校长进人。同样,内存也是这样,内存的3个部分,不是所有的东西都能存进去的。 

        堆:由 malloc 系列函数或 new 操作符分配的内存。其生命周期由 free 或 delete 决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。

        栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限。

        静态区:保存自动全局变量和 static 变量(包括 static 全局和局部变量)。静态区的内容在整个程序的生命周期内都存在,由编译器在编译的时候分配。

3. 常见的内存错误及对策 

3.1 指针没有指向一块合法的内存 

定义了指针变量,但是没有为指针分配内存,即指针没有指向一块合法的内存。浅显的例子就不举了,这里举几个比较隐蔽的例子。 

3.1.1 结构体成员指针未初始化 

  • 很多初学者犯了这个错误还不知道是怎么回事。
  • 这里定义了结构体变量 stu,但是他没想到这个结构体内部 char *name,该成员在定义结构体变量 stu时,只是给 name这个指针变量本身分配了4字节;
  • name 指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在调用 strcpy 函数时,会将字符串 “Tom" 往乱码所指的内存上复制,而这块内存 name 指针根本就无权访问,导致出错。
  • 解决的办法是为 name 指针 malloc 一块空间。

正确示例: 

同样,也有人犯如下错误: 

为指针变量 pstu 分配了内存,但是同样没有给 name 指针分配内存。错误与上面第 1 种情况一样,解决的办法也一样。这里用了一个 malloc 给人一种错觉,以为也给 name 指针分配了内存。 

3.1.2 没有为结构体指针分配足够的内存 

为 pstu 分配内存的时候,分配的内存大小不合适。这里把 sizeof(struct student)误写为sizeof(struct student*)。当然name指针同样没有被分配内存。解决办法同上。 

3.1.3 函数的入口校验 

不管什么时候,我们使用指针之前一定要确保指针是有效的。

 一般在函数人口处使用 assert(NULL!=p)对参数进行校验。在非参数的地方使用 if(NULL!= p) 来校验。但这都有一个要求,即 p 在定义的同时被初始化为 NUL L。

        比如上面的例子,使用 if (NULL!=p) 校验也起不了作用,因为 name 指针并没有被初始化为NULL,其内部是一个非 NULL 的乱码。

         assert 是一个宏,而不是函数,包含在 assert.h 头文件中。如果其后面括号里的值为假,则程序终止运行,并提示出错;如果后面括号里的值为真,则继续运行后面的代码。这个宏只在Debug版本上起作用,而在 Release 版本中被编译器完全优化掉,这样就不会影响代码的性能。

        有人也许会问,既然在 Release 版本中被编译器完全优化掉,那 Release 版本是不是就完全没有这个参数人口校验了呢?这样的话那不就跟不使用它效果一样吗?

        是的,使用 assert 宏的地方在 Release 版本里面确实没有这些校验。但是我们要知道,assert 宏只是帮助我们调试代码用的,它的一切作用就是让我们尽可能地在调试函数的时候把错误排除掉,而不是等到 Release之后。它本身并没有除错功能。再有一点就是,参数出现错误并非本函数有问题,而是调用者传过来的实参有问题。assert 宏可以帮助我们定位错误,而不是排除错误。 

3.2  为指针分配的内存太小

为指针分配了内存,但是内存大小不够,导致出现越界错误。 

char* p1 = "abcdefg";
char* p2 = (char*)malloc(sizeof(char)*strlen(p1));
strcpy(p2, p1);

        p1 是字符串常量,其长度为 7 个字符,但其所占内存大小为 8 字节。初学者往往忘了字符串常量的结束标志 “\0”,这样的话将导致 p1 字符串中最后一个空字符 “\0" 没有被复制到 p2 中。解决的办法是加上这个字符串结束标志符:

char* p2 = (char*)malloc(sizeof(char)*strlen(p1) + sizeof(char)*1);

        另外,不要因为 char 类型大小为 1字节就省略 sizof(char) 这种写法,这样只会使你的代码可移植性下降。 

        这里需要注意的是,只有字符串常量才有结束标志符,比如下面这种写法就没有结束标志符了:

char a[7] = {'a', 'b', 'c', 'd', 'e', 'f', 'g'}

 3.3 内存分配成功,但并未初始化

犯这个错误往往是由于没有初始化的概念或者是以为内存分配好之后其值自然为 0。未初始化指针变量也许看起来不那么严重,但是它确确实实是个非常严重的问题,而且往往出现这种错误很难找到原因。 

也许这种严重的问题并不多见,但是也绝不能掉以轻心。因此在定义一个变量时,第一件事就是初始化。你可以把它初始化为一个有效的值,比如:

int i = 10;
char* p = (char*)malloc(sizeof(char));

但是往往刚定义的时候我们还不确定这个变量的初值,这样的话可以初始化为 0 或 NULL: 

int i = 0;
char* p = NULL;

如果定义的是数组,则可以这样初始化:

int a[10] = {0};

或者用 memset 函数来初始化为 0: 

memset(a,0,sizeof(a));

memset 函数有 3 个参数:第 1 个参数是要被设置的内存起始地址;第 2 个参数是要被设置的值;第 3 个参数是要被设置的内存大小,单位为字节。这里并不想过多地讨论 memset 函数的用法,如果想了解更多,请参考相关资料。 

3.4 内存越界 

内存分配成功,且已经初始化,但是操作越过了内存的边界。这种错误经常是由于操作数组或指针时出现“多1"或“少1"而出现的,比如: 

int a[10] = { 0 };
for(i = 0; i <= 10; i++)
{a[i] = i;
}

所以,for循环的循环变量一定要使用半开半闭的区间,而且如果不是特殊情况,循环变量尽量从0开始。 

3.5 内存泄漏 

        内存泄漏几乎是很难避免的,不管是老手还是新手,都存在这个问题。甚至包括Windows、Linux 这类软件,都或多或少有内存泄漏。也许对于一般的应用软件来说,这个问题似乎不是那么突出,重启一下也不会造成太大损失。但是如果你开发的是嵌人式系统软件,比如汽车制动系统、心脏起搏器等对安全要求非常高的系统,你总不能让心脏起搏器重启吧,人家阎王老爷是非常好客的。

        会产生泄漏的内存就是堆上的内存(这里不讨论资源、句柄等泄漏情况),也就是说由 malloc 系列函数或 new 操作符分配的内存。如果用完之后没有及时 free 或 delete,这块内存就无法释放,直到整个程序终止。

3.5.1 告老还乡求田 

这里看一下小故事: 

3.5.2 如何使用 malloc 函数 

3.5.3 用 malloc 申请 0 字节内存

3.5.4 内存释放 

既然有分配,那就必须有释放。不然的话,有限的内存总会用光,而没有释放的内存却在空闲。与 malloc 对应的就是free 函数了。free 函数只有一个参数,就是所要释放的内存块的首地址,接上例则为: 

free(p);

3.5.5 内存释放之后 

        既然使用 free 函数之后指针变量 p 本身保存的地址并没有改变,那我们就需要重新把p的值变为NULL: 

p = NULL;

        这个 NULL 就是我们前面所说的“栓野狗的链子”,如果你不栓起来迟早会出问题的。比如:在 free(p)之后,你用 if(NULL!= p) 这样的校验语句还能起作用吗? 

释放完块内存之后,没有把指针置NULL,这个指针就成为了“野指针”,这是很危险的,而且也是经常出错的地方。所以一定要记住一条:free完之后,一定要给指针置NULL。

3.5.6 内存已经被释放了,但是继续通过指针来使用 

完结撒花!~ 欢迎订阅本专栏!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/33195.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Mybatis动态sql标签

动态SQL标签简介: MyBatis的一个强大的特性之一通常是它的动态SQL能力。如果你有使用JDBC或其他相似框架的经验,你就明白条件地串联SQL字符串在一起是多么的痛苦,确保不能忘了空格或在列表的最后省略逗号。动态SQL可以彻底处理这种痛苦。 Mybatis中实现动态sql的标签有&#x…

【论文速读】|利用大语言模型实现现实世界代码的翻译:一项针对翻译到Rust语言的研究

本次分享论文&#xff1a;Towards Translating Real-World Code with LLMs: A Study of Translating to Rust 基本信息 原文作者&#xff1a;Hasan Ferit Eniser, Hanliang Zhang, Cristina David, Meng Wang, Maria Christakis, Brandon Paulsen, Joey Dodds, Daniel Kroeni…

Maven之介绍

目录 一、简介 &#xff08;2&#xff09;为什么学习Maven&#xff1f; 二、小结 一、简介 &#xff08;1&#xff09;Maven 是一个 Java 项目管理和构建工具。它可以定义项目结构、项目依赖&#xff0c;并使用统一的方式进行自动化构建&#xff0c;是Java项目不可缺少的工具…

原装GUVCL-T10GD韩国GENICOM光电二极管紫外线传感器原厂代理商

深圳市宏南科技有限公司是韩国GenUV公司的原厂代理商&#xff0c;所售紫外线传感器均来自于原始生产厂商直接供货&#xff0c;非第三方转售。 GUVCL-T10GD 韩国GENICOM光电二极管光传感器 / 低亮度 / 紫外线 UV-C传感器 GUVCL-T10GD 采用基于氮化铟的材料 肖特基型 光电二极管…

Linux线程:基于环形队列RingQueue的生产消费者模型

目录 一、环形队列的概念及定义 二、POSIX信号量 三、RingQueue的实现方式 3.1RingQueue.hpp的构建 3.2Thread.hpp 3.3Main.cc主函数的编写 3.4Task.hpp function包装器的使用 一、环形队列的概念及定义 此处的环形队列并不是一个真正意义上的环&#xff0c;而是通过对容…

微信小程序之横向列表展示

效果图 参考微信小程序可看 代码&#xff1a; <view class"lbtClass"><view class"swiper-container"><scroll-view class"swiper" scroll-x"true" :scroll-left"scrollLeft"><block v-for"(six…

centos7.5 安装mongo客户端

参考&#xff1a; https://doc.hcs.huawei.com/zh-cn/usermanual/dds/dds_faq_0018.html https://www.cnblogs.com/zhaoyingjie/p/17784968.html mongo 和 mongosh 的区别database - Difference in mongo --version and mongosh --version - Stack Overflow mongoDB 5.0 之后…

4.1 四个子空间的正交性

一、四个子空间的正交性 如果两个向量的点积为零&#xff0c;则两个向量正交&#xff1a; v ⋅ w v T w 0 \boldsymbol v\cdot\boldsymbol w\boldsymbol v^T\boldsymbol w0 v⋅wvTw0。本章着眼于正交子空间、正交基和正交矩阵。两个子空间的中的向量&#xff0c;一组基中的向…

Python多语言欧拉法和预测校正器实现

&#x1f4dc;流体力学电磁学运动学动力学化学和电路中欧拉法 &#x1f4dc;流体力学电磁学运动学动力学化学和电路中欧拉法示例&#xff1a;Python重力弹弓流体晃动微分方程模型和交直流电阻电容电路 ✒️多语言实现欧拉法和修正欧拉法 在数学和计算科学中&#xff0c;欧拉…

C# VTK 移动旋转

对vtk 场景中一个或多个选中物体进行移动旋转。 交互移动旋转坐标系 首先我们创建旋转的交互坐标系&#xff0c;三个移动Actor&#xff0c;三个旋转Actor&#xff0c;还需要4个定位坐标的小球Actor。 public class CoordinateActor 中添加Actor// 当前选中的Actorpublic vtkAc…

C语言---数据结构(1)--时间复杂和空间复杂度计算

1.什么是时间复杂度和空间复杂度 1.1算法效率 算法效率分为时间效率和空间效率 时间效率被称为时间复杂度&#xff0c;而空间效率被称作空间复杂度。 时间复杂度主要衡量的是一个算法的运行速度&#xff0c;而空间复杂度主要衡量一个算法所需要的额外空间&#xff0c;在计算…

机器人阻抗控制相关文献学习(阻抗实现)

机器人阻抗是一个描述机器人与环境交互时动态特性的概念。 定义&#xff1a; 阻抗在机器人领域中&#xff0c;通常用来描述机器人与其环境之间的相互作用。当机器人与环境接触时&#xff0c;环境对机器人施加一个作用力&#xff0c;而机器人也会对环境施加一个反作用力。这个反…

PMP与软考的区别? 我该学习哪个?

PMP&#xff08;项目管理专业人士&#xff09;和软考&#xff08;软件工程师考试&#xff09;是两种不同领域的认证考试&#xff0c;分别专注于项目管理和软件工程师领域。下面将对它们做详细介绍。 一、PMP PMP作为项目管理领域的国际认证考试&#xff0c;由美国项目管理协会…

WordPress CDN是什么?CDN有什么作用?

您想让您的网站加载速度更快吗&#xff1f; 网站所有者希望网站加载速度快&#xff0c;内容丰富&#xff0c;功能强大&#xff0c;吸引用户。然而&#xff0c;添加这些功能可能会降低网站速度&#xff0c;难以快速向全球用户提供内容。 这就是为什么许多WordPress网站使用 CDN…

小型数据中心是什么?如何建设?

在数字化时代&#xff0c;小型数据中心正成为许多企业和组织加强数据管理和服务扩展的理想选择。与传统大型数据中心相比&#xff0c;小型数据中心以其灵活性、高效性和相对较低的运营成本吸引着越来越多的关注。然而&#xff0c;要成功建设一个小型数据中心&#xff0c;并确保…

Web网页端IM产品RainbowChat-Web的v7.0版已发布

一、关于RainbowChat-Web RainbowChat-Web是一套Web网页端IM系统&#xff0c;是RainbowChat的姊妹系统&#xff08;RainbowChat是一套基于开源IM聊天框架 MobileIMSDK (Github地址) 的产品级移动端IM系统&#xff09;。 ► 详细介绍&#xff1a;http://www.52im.net/thread-2…

特氟龙FEP离心管50ml30ml圆底赛默飞耐酸碱Thermo3114-0050离心管

FEP离心管&#xff1a;又叫聚全氟乙丙烯离心管&#xff0c;特氟龙离心管&#xff0c;F46离心管等。 其主要特性有&#xff1a; 1、可耐高温205℃、耐腐蚀性、不吸附性、透明可见、方便实验操作&#xff1b; 2、可适配于国内外各厂家离心机使用。 3、内壁光滑&#xff0c;不…

解决安全规模问题:MinIO 企业对象存储密钥管理服务器

在强大可靠的存储解决方案领域&#xff0c;MinIO 作为持久层脱颖而出&#xff0c;为组织提供安全、持久和可扩展的存储选项。MinIO 通常负责处理关键任务数据&#xff0c;在确保高可用性方面发挥着至关重要的作用&#xff0c;有时甚至在全球范围内。存储数据的性质&#xff0c;…

电路学习——经典运放电路(2024.06.21)

参考链接1: 11个经典运放电路 在此感谢各位前辈大佬的总结&#xff0c;写这个只是为了记录学习大佬资料的过程&#xff0c;内容基本都是搬运的大佬博客&#xff0c;觉着有用自己搞过来自己记一下&#xff0c;如果有大佬觉着我搬过来不好&#xff0c;联系我删。 电路学习——经典…