《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,一经查实,立即删除!

相关文章

智能推荐系统:技术解析与实践指南

智能推荐系统&#xff1a;技术解析与实践指南 背景与挖掘目标 在互联网信息爆炸的今天&#xff0c;用户在海量内容中筛选感兴趣的信息变得日益困难。因此&#xff0c;搜索引擎结合推荐系统的模式应运而生。本章节将深入探讨推荐系统&#xff0c;其核心目标如下&#xff1a; …

Mybatis动态sql标签

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

【Help】Ubuntu卸载原生版本python后图像化界面消失

1. 开机进入tty界面&#xff0c;登录账号和密码 2. ping www.baidu.com 检测网络连接 2.1 若出现菱形乱码&#xff0c;则修改语言位英文 LANG"en_US.UTF-8" LANGUAGE"en_US:en" 2.2 如果没有网络连接&#xff0c;则 查找可用wifi&#xff1a;nmcli dev …

【论文速读】|利用大语言模型实现现实世界代码的翻译:一项针对翻译到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项目不可缺少的工具…

《FFmpeg开发实战:从零基础到短视频上线》资源下载和内容勘误

资源下载 下面是《FFmpeg开发实战&#xff1a;从零基础到短视频上线》一书用到的工具和代码资源&#xff1a; 1、本书使用的FFmpeg版本为FFmpeg 5.1.2&#xff0c;也可在FFmpeg的github主页上下载最新的FFmpeg源码。 2、本书第12章使用的Android Studio版本为Android Studio D…

原装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 之后…

node-sass install 失败

安装失败多数是因为下载失败&#xff0c;或者node版本不对&#xff0c;node版本不对可以通过nvm去管理node,下载不同版本的node,通过nvm去切换不同版本的node去兼容不同的项目。 这篇文档主要说明下载失败的问题如何解决&#xff1a; 可以通过npm config list 查看npm镜像源的…

4.1 四个子空间的正交性

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

专业与学校的权衡。

一场考试落下帷幕&#xff0c;新的思考与选择悄然来临。对于每一位高考考生&#xff0c;学校和专业选择是开启大学新生活的两个前置必选项。分数受限的条件下&#xff0c;“鱼与熊掌不可兼得”&#xff0c;到底是选择一个心仪的专业还是选择一个知名度更高的学校&#xff1f;这…

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

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

C# VTK 移动旋转

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

【华为OD机试】获取最大软件版本号(C++ Java JavaScript Python )

题目 题目描述 Maven 版本号定义,<主版本>.<次版本>.<增量版本>-<里程碑版本>,举例3.1.4-beta 其中,主版本和次版本都是必须的,主版本,次版本,增量版本由多位数字组成,可能包含前导零,里程碑版本由字符串组成。 <主版本>.<次版本>…

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

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

fontspider 字体筛选工具

在项目中为了使包体大小优化 有时候字体太大 需要筛选出部分文字的字体字体 筛选优化包体 fontspider 字体筛选工具 筛选工具下载传送门https://download.csdn.net/download/qq_38147639/89477482

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

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