vs使用未初始化的内存怎么解决_遇到C语言内存错误怎么办?一定要找准这六个原因...

一、没有为指针分配内存

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

a8ce57755d446bb3b22bfce92c46d5b8.png

1、结构体成员指针未初始化

struct student

{

char *name;

int score;

}stu,*pstu;

int main()

{

strcpy(stu.name,"Jimy");

stu.score = 99;

return 0;

}

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

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

int main()

{

pstu = (struct student*)malloc(sizeof(struct student));

strcpy(pstu->name,"Jimy");

pstu->score = 99;

free(pstu);

return 0;

}

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

d4bef91a96e1d5785b340a31f1b10a51.png

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

int main()

{

pstu = (struct student*)malloc(sizeof(struct student*));

strcpy(pstu->name,"Jimy");

pstu->score = 99;

free(pstu);

return 0;

}

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

3、函数的入口校验

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

一般在函数入口处使用assert(NULL != p)对参数进行校验。在非参数的地方使用if(NULL != p)来校验。但这都有一个要求,即p 在定义的同时被初始化为NULL 了。比如上面的例子,即使用if(NULL != p)校验也起不了作用,因为name 指针并没有被初始化为NULL,其内部是一个非NULL 的乱码。

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

03d802df5207dbf5419e6a4a56b3558d.png

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

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

二、为指针分配的内存太小

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

char *p1 = “abcdefg”;

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

strcpy(p2,p1);

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

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

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

char a[7] = {‘a’,’b’,’c’,’d’,’e’,’f’,’g’};

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

4af064605972dd3d39584c5e2d97a6a7.png

三、 内存越界

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

int a[10] = {0};

for (i=0; i<=10; i++)

{

a[i] = i;

}

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

9aa66c9c4cad6ba64103892072e19724.png

四、 给指针分配内存后,没有初始化内存

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

曾经有一个学生在写一个windows 程序时,想调用字库的某个字体。而调用这个字库需要填充一个结构体。他很自然的定义了一个结构体变量,然后把他想要的字库代码赋值给了相关的变量。但是,问题就来了,不管怎么调试,他所需要的这种字体效果总是不出来。我在检查了他的代码之后,没有发现什么问题,于是单步调试。在观察这个结构体变量的内存时,发现有几个成员的值为乱码。就是其中某一个乱码惹得祸!因为系统会按照这个结构体中的某些特定成员的值去字库中寻找匹配的字体,当这些值与字库中某种字体的某些项匹配时,就调用这种字体。但是很不幸,正是因为这几个乱码,导致没有找到相匹配的字体!因为系统并无法区分什么数据是乱码,什么数据是有效的数据。只要有数据,系统就理所当然的认为它是有效的。

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

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 函数有三个参数,第一个是要被设置的内存起始地址;第二个参数是要被设置的值;第三个参数是要被设置的内存大小,单位为byte。这里并不想过多的讨论memset 函数的用法,如果想了解更多,请参考相关资料。

至于指针变量如果未被初始化,会导致if 语句或assert 宏校验失败。

13e747948471020dd90d28568fe0a694.png

五、没有释放申请的内存,导致内存泄露

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

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

1、告老还乡求良田

怎么去理解这个内存分配和释放过程呢?先看下面这段对话:

万岁爷:爱卿,你为朕立下了汗马功劳,想要何赏赐啊?

某功臣:万岁,黄金白银,臣视之如粪土。臣年岁已老,欲告老还乡。臣乞良田千亩以荫后世,别无他求。

万岁爷:爱卿,你劳苦功高,却仅要如此小赏,朕今天就如你所愿。户部刘侍郎,查看湖广一带是否还有千亩上等良田未曾封赏。

刘侍郎:长沙尚有五万余亩上等良田未曾封赏。

万岁爷:在长沙拨良田千亩封赏爱卿。爱卿,良田千亩,你欲何用啊?

某功臣:谢万岁。长沙一带,适合种水稻,臣想用来种水稻。种水稻需要把田分为一亩一块,方便耕种。

。。。。

fd6c80f5e9d06d75d6b255081c2af19a.png

2、如何使用malloc 函数

不要莫名其妙,其实上面这段小小的对话,就是malloc 的使用过程。malloc 是一个函数,专门用来从堆上分配内存。使用malloc 函数需要几个要求:

内存分配给谁?这里是把良田分配给某功臣。

分配多大内存?这里是分配一千亩。

是否还有足够内存分配?这里是还有足够良田分配。

内存的将用来存储什么格式的数据,即内存用来做什么?

这里是用来种水稻,需要把田分成一亩一块。分配好的内存在哪里?这里是在长沙。

如果这五点都确定,那内存就能分配。下面先看malloc 函数的原型: (void *)malloc(int size) malloc 函数的返回值是一个void 类型的指针,参数为int 类型数据,即申请分配的内存大小,单位是byte。内存分配成功之后,malloc 函数返回这块内存的首地址。你需要一个指针来接收这个地址。但是由于函数的返回值是void *类型的,所以必须强制转换成你所接收的类型。也就是说,这块内存将要用来存储什么类型的数据。比如: char *p = (char *)malloc(100); 在堆上分配了100 个字节内存,返回这块内存的首地址,把地址强制转换成char *类型后赋给char *类型的指针变量p。同时告诉我们这块内存将用来存储char 类型的数据。也就是说你只能通过指针变量p 来操作这块内存。这块内存本身并没有名字,对它的访问是匿名访问。

上面就是使用malloc 函数成功分配一块内存的过程。但是,每次你都能分配成功吗?

不一定。上面的对话,皇帝让户部侍郎查询是否还有足够的良田未被分配出去。使用malloc函数同样要注意这点:如果所申请的内存块大于目前堆上剩余内存块(整块),则内存分配会失败,函数返回NULL。注意这里说的“堆上剩余内存块”不是所有剩余内存块之和,因为malloc 函数申请的是连续的一块内存。

既然malloc 函数申请内存有不成功的可能,那我们在使用指向这块内存的指针时,必须用if(NULL != p)语句来验证内存确实分配成功了。

3、用malloc 函数申请0 字节内存

另外还有一个问题:用malloc 函数申请0 字节内存会返回NULL 指针吗?

可以测试一下,也可以去查找关于malloc 函数的说明文档。申请0 字节内存,函数并不返回NULL,而是返回一个正常的内存地址。但是你却无法使用这块大小为0 的内存。这好尺子上的某个刻度,刻度本身并没有长度,只有某两个刻度一起才能量出长度。对于这一点一定要小心,因为这时候if(NULL != p)语句校验将不起作用。

9cc569b3e9b02aa36e848e542827232e.png

4、内存释放

既然有分配,那就必须有释放。不然的话,有限的内存总会用光,而没有释放的内存却在空闲。与malloc 对应的就是free 函数了。free 函数只有一个参数,就是所要释放的内存块的首地址。比如上例: free(p); free 函数看上去挺狠的,但它到底作了什么呢?其实它就做了一件事:斩断指针变量与这块内存的关系。比如上面的例子,我们可以说malloc 函数分配的内存块是属于p 的,因为我们对这块内存的访问都需要通过p 来进行。free 函数就是把这块内存和p 之间的所有关系斩断。从此p 和那块内存之间再无瓜葛。至于指针变量p 本身保存的地址并没有改变,但是它对这个地址处的那块内存却已经没有所有权了。那块被释放的内存里面保存的值也没有改变,只是再也没有办法使用了。

这就是free 函数的功能。按照上面的分析,如果对p 连续两次以上使用free 函数,肯定会发生错误。因为第一使用free 函数时,p 所属的内存已经被释放,第二次使用时已经无内存可释放了。关于这点,我上课时让学生记住的是:一定要一夫一妻制,不然肯定出错。

malloc 两次只free 一次会内存泄漏;malloc 一次free 两次肯定会出错。也就是说,在程序中malloc 的使用次数一定要和free 相等,否则必有错误。这种错误主要发生在循环使用malloc 函数时,往往把malloc 和free 次数弄错了。这里留个 练习:

写两个函数,一个生成链表,一个释放链表。两个函数的参数都只使用一个表头指针。

5、内存释放之后

既然使用free 函数之后指针变量p 本身保存的地址并没有改变,那我们就需要重新把p的值变为NULL: p = NULL; 这个NULL 就是我们前面所说的“栓野狗的链子”。如果你不栓起来迟早会出问题的。比如:在free(p)之后,你用if(NULL != p)这样的校验语句还能起作用吗?例如:

char *p = (char *)malloc(100);

strcpy(p, “hello”);

free(p); /* p 所指的内存被释放,但是p 所指的地址仍然不变*/

if (NULL != p)

{

/* 没有起到防错作用*/

strcpy(p, “world”); /* 出错*/

}

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

同时留一个问题:对NULL 指针连续free 多次会出错吗?为什么?如果让你来设计free函数,你会怎么处理这个问题?

51008a24a0f8a737ffe3c4dc9cc8a1a0.png

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

这里一般有三种情况:

第一种:就是上面所说的,free(p)之后,继续通过p 指针来访问内存。解决的办法就是给p 置NULL。

第二种:函数返回栈内存。这是初学者最容易犯的错误。比如在函数内部定义了一个数组,却用return 语句返回指向该数组的指针。解决的办法就是弄明白栈上变量的生命周期。

第三种:内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办法是重新设计程序,改善对象之间的调用关系。

20bfcabbe572c1b0f08eff2ed0a35e3d.png

上面详细讨论了常见的六种错误及解决对策,希望读者仔细研读,尽量使自己对每种错误发生的原因及预防手段烂熟于胸。一定要多练,多调试代码,同时多总结经验。

最后,如果你想更好的提升你的编程能力,好好学习C/C++编程知识的话!那么你很幸运~

点击下方“了解更多”加入C语言/C++企鹅圈,这里还有一些你可能不知道的趣事分享哟。

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

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

相关文章

cad求和插件_黑科技 | 无BIM建模下平面CAD自动生成门窗表

如果你接到的施工图既不是用天正出的&#xff0c;也不是用revit出的&#xff0c;还得统计门窗表&#xff0c;那么你需要读完这篇文章。为了能够让自己和所有底层同行们从这项无脑又烧脑的机械劳动中解脱&#xff0c;C君近期利用茶余饭后的时间开发了一个小插件&#xff0c;可以…

linux数据库实例开机启动,linux下数据库实例开机自启动设置

linux下数据库实例开机自启动设置 1、修改/oratab [rootorg54 ~]# vi/etc/oratab --把N改为Y&#xff0c;如下提示 # This file is used by ORACLEutilities. It is created by root.sh # and updated by the Database ConfigurationAssistant when creating # a datablinux下数…

tensorboard ckpt pb 模型的输出节点_算法工程化系列——模型固化

摘要基于tensorflow训练的模型一般被保存为ckpt形式的文件&#xff0c;随着当前深度学习模型网络越来越大&#xff0c;对应模型也会非常大。当对外提供服务的时候&#xff0c;如果采用ckpt的形式&#xff0c;服务进程被调起来非常困难&#xff0c;且推理服务一般速度也较慢(会达…

深度linux内核升级,深度操作系统 2020.11.11 更新发布:内核升级

原标题&#xff1a;深度操作系统 2020.11.11 更新发布&#xff1a;内核升级IT之家11月11日消息 今日&#xff0c;深度操作系统宣布2020.11.11 更新现已发布。本次更新包括升级内核、Debian 10.6 仓库以及系统安全性更新。系统安全方面&#xff0c;本次更新修复了 Firefox-ESR 安…

python爬电影_使用Python多线程爬虫爬取电影天堂资源

最近花些时间学习了一下Python&#xff0c;并写了一个多线程的爬虫程序来获取电影天堂上资源的迅雷下载地址&#xff0c;代码已经上传到GitHub上了&#xff0c;需要的同学可以自行下载。刚开始学习python希望可以获得宝贵的意见。 先来简单介绍一下&#xff0c;网络爬虫的基本实…

sentinel 端口_Sentinel原理:控制台是如何获取到实时数据的

Sentinel 系列教程&#xff0c;现已上传到 github 和 gitee 中&#xff1a;GitHub&#xff1a;https://github.com/all4you/sentinel-tutorialGitee&#xff1a;https://gitee.com/all_4_you/sentinel-tutorialSentinel 能够被大家所认可&#xff0c;除了他自身的轻量级&#x…

python 最小二乘回归 高斯核_「机器学习」一文读懂线性回归、岭回归和Lasso回归...

点击上方蓝色字体&#xff0c;关注AI小白入门哟作者 | 文杰编辑 | yuquanle本文介绍线性回归模型&#xff0c;从梯度下降和最小二乘的角度来求解线性回归问题&#xff0c;以概率的方式解释了线性回归为什么采用平方损失&#xff0c;然后介绍了线性回归中常用的两种范数来解决过…

优先队列默认是小顶堆吗_一分钟带你读懂什么是堆?

堆其实就是一种特殊的队列——优先队列。 普通的队列游戏规则很简单&#xff1a;就是先进先出&#xff1b;但这种优先队列搞特殊&#xff0c;不是按照进队列的时间顺序&#xff0c;而是按照每个元素的优先级来比拼&#xff0c;优先级高的在堆顶。 这也很容易理解吧&#xff0c;…

python 注释一段话_Python快速入门(一)

引言Python作为一个&#xff0c;目前最火的编程语言之一&#xff0c;已经渗透到了各行各业。它易学好懂&#xff0c;拥有着丰富的库&#xff0c;功能齐全。人生苦短&#xff0c;就用Python。这个快速入门系列分为六篇&#xff0c;包含了Python大部分基础知识&#xff0c;每篇阅…

linux ibus获取窗体位置,Ubuntu 12.04 显示ibus 的输入框

在虚拟机中安装了Ubuntu 12.04&#xff0c;系统是英文版本的&#xff0c;我能接受&#xff0c;但是苦于没有中文输入法。起先&#xff0c;我是安装SCIM&#xff0c;结果我折腾了半天&#xff0c;发现其只能在lib-office下使用。firefox,文字编辑器中都不能调出SCIM。无奈将其卸…

python批量下载文件教程_Python抓包菜鸟教程:批量下载图片的方法,电脑和手机都能用...

笔者看上了一组图集&#xff0c;然后准备一张一张下载时&#xff0c;瞄了一眼&#xff0c;这组图集还有100&#xff0c;好吧&#xff0c;我酸了。 笔者就是试试工具&#xff0c;你们别像我这样用&#xff0c;这么好的工具&#xff0c;做自媒体&#xff0c;那绝对了那如何批量下…

esxi挂载Linux的nfs盘,ESXi安装centos7挂载群晖NFS

前段时间折腾了ESXi&#xff0c;然后无尽的折腾接踵而来&#xff0c;今天要说的是如何安装centos7并挂载群晖虚拟机的NFS共享文件夹直接步入正题&#xff01;先是下载centos7镜像&#xff0c;因为我是用来当服务器的&#xff0c;所以只需要minimal版即可【centos下载链接】自己…

传统的6d位姿估计fangfa1_李飞飞团队最新论文:基于anchor关键点的类别级物体6D位姿跟踪...

点击上方“3D视觉工坊”&#xff0c;选择“星标”干货第一时间送达简介作者提出了一种基于RGB-D的深度学习方法6PACK&#xff0c;能够实时的跟踪已知类别物体。通过学习用少量的3D关键点来简洁地表示一个物体&#xff0c;基于这些关键点&#xff0c;通过关键点匹配来估计物体在…

安卓system镜像分区_玩机爱好者想要的PT分区到底是什么?可以使现有的安卓系统更快!...

小编第一次看见PT分区这个词。就比较好奇他到底是什么神仙技术。今天&#xff0c;小编给大家科普一下&#xff0c;可能小编理解的也不是特别准确&#xff0c;请各位谅解&#xff01;&#xff01; 欢迎关注小编。各位玩机爱好者总是沉浸在各种ROM包、第三发Rec&#xff0c;以及各…

python求和1到100_python等差数列求和公式前 100 项的和实例

python等差数列求和公式前 100 项的和实例 最近跑去学了下python,一个很简单的题&#xff0c;结果发现数学公示忘了&#xff0c;在不用for循环的情况下居然有些懵&#xff0c;记录为下.. 题&#xff1a;等差数列可以定义为每一项与它的前一项的差等于一个常数&#xff0c;可以用…

jupyter kernel_新乡联通案例分享:Jupyter开发环境配置的常用技巧

Jupyter开发环境配置的常用技巧新乡联通网管中心 邢少华Python开发环境中&#xff0c;大部分人使用的是Jupyter&#xff0c;在Jupyter中有几个令人困扰的问题&#xff1a;1. Jupyter的默认打开目录如何修改2. Jupyter默认使用的浏览器如何修改3. 好用的Jupyter插件如何安装4.…

二叉树 中序遍历 python_LeetCode 105 树 从前序与中序遍历序列构造二叉树(Medium)

17(105) 从前序与中序遍历序列构造二叉树(Medium)描述根据一棵树的前序遍历与中序遍历构造二叉树。注意: 你可以假设树中没有重复的元素。示例例如&#xff0c;给出前序遍历 preorder [3,9,20,15,7] 中序遍历 inorder [9,3,15,20,7]返回如下的二叉树&#xff1a;3/ 9 20/ 1…

计算payload长度c语言,C语言0长度数组(可变数组/柔性数组)详解

1 零长度数组概念众所周知, GNU/GCC 在标准的 C/C 基础上做了有实用性的扩展, 零长度数组(Arrays of Length Zero) 就是其中一个知名的扩展.多数情况下, 其应用在变长数组中, 其定义如下struct Packet{ int state; int len; char cData[0]; //这里的0长结构体就为变长结构体提供…

go语言调用c 的头文件 so,golang 学习(10): 使用go语言调用c语言的so动态库-Go语言中文社区...

一、前言最近在学习go&#xff0c;因为需要调用c语言打包成的so动态库里面的方法&#xff0c;避免自己再去造轮子&#xff0c;所以想直接使用golang调用so&#xff0c;但是参考了其他博客大佬写的&#xff0c;我每一步原封不动的写下来&#xff0c;结果都是一堆错误&#xff0c…

android 开机动画 渐变,[Parallax Animation]实现知乎 Android 客户端启动页视差滚动效果...

前言Parallax Scrolling (视差滚动)&#xff0c;是一种常见的动画效果。视差一词来源于天文学&#xff0c;但在日常生活中也有它的身影。在疾驰的动车上看风景时&#xff0c;会发现越是离得近的&#xff0c;相对运动速度越快&#xff0c;而远处的山川河流只是缓慢的移动着&…