《0bug-C/C++商用工程之道》节选00--内存管理的基本要求

最近被朋友们老是问到一些嵌入式的问题,很多是技术底层细节的问题,很不好回答,因为涉及到技术,要么不写,要写就要长篇大论,太费精力,也不太适合在网上上讨论。

想了一下,干脆这样,我将在博客上不定期、部分公布我的《0bug-C/C++商用工程之道》一书中的章节段落,有兴趣的朋友可以看看。

其实回过头再看这本书,感觉这一年多做数据库,对于书中很多技术又有了不少新的看法,在公布的过程中,我也会将自己新的一些观点补充一下,也欢迎有兴趣的朋友,针对技术观点,发邮件讨论。

我的QQ是712123,常用的邮箱是tonyxiaohome@hotmail.com

在考量这个分享内容的时候,我想首先从内存讨论开始吧,因为我觉得作为C语言程序员,无论是在哪个平台开发,对于内存的掌控是必须的,可以说是重中之重的技术。

一切从内存开始!

所以我挑选了一下,从第七章《内存及资源管理》开始分享。

C语言是公认的一门中低级语言,主要的原因就是其提供了类似于汇编语言的指针调用,将编译器和操作系统内部的很多核心机密,向应用程序公开,使我们的程序,可以自由地使用动态内存申请,指针管理等操作系统级的功能,实现强大的程序能力。

这实际上是把操作系统对内存的管理,向程序员做了公开,程序员可以站在系统的角度,进行动态的内存资源调度,这给CC++程序员带来了莫大的方便性,获得了强大的控制能力,但同时,也给CC++程序带来了天生的安全隐患。

因此,作为一个商业化的CC++程序员,首先就需要熟练掌握对内存,以及各种系统资源的操作能力,能做到不泄露、不溢出、安全使用。这对程序员的综合实力,提出了较高的要求。笔者在本章,将为大家展示对系统内存,以及各个系统资源实施管控的综合技巧和原则。

7.1  内存管理的基本要求

其实很多高级语言,如JavaPython,都有自己的内存管理器,应用程序一般尽管使用变量即可,程序员很少关心变量失效之后的摧毁问题,更无需关心内存的优化使用,减少内存碎片等细节。

不过,CC++语言,把系统内存直接暴露给程序员使用,看似提升了灵活性和方便性,但同时,也放弃了更高级的内存管控机制,这对程序员提出了很高的理论和实战能力的要求,稍有不慎,即会出现bug

笔者经过多年的分析,认为如果要彻底杜绝内存相关的bug,实现CC++语言无错化程序设计,程序员有必要在CC++提供的基本内存操作的基础上,自行构建一个更加合理的内存管理机,帮助程序员实现内存的安全、高效访问。

这个内存管理机,笔者称之为“内存池”,本小节即试图论述其基本的设计需求以及解决方案。

7.1.1  不泄露

由于CC++语言中,内存的申请和释放,一定是二元动作,需要程序员显式地调用相关函数,对称地完成内存操作,才能保证不泄露内存。

这对很多情况下的程序开发,提出了较高的要求,笔者前文花了大量的篇幅,向大家介绍二元动作操作的常见手法,以期避免内存泄漏等bug

不过,这些动作一般都是程序员的行为,我们知道,程序员是人,是人就有可能犯错误,纯粹的手工操作规范,并不足以杜绝内存bug的产生。

于是笔者就设想,如果在CC++传统的内存管理机制之外,我们自行构建一种内存的管理机制,能在程序员忘了释放内存时,主动替其释放,则可望大大减少内存相关的bug。因此,内存池的第一个设计目标,是主动替程序员完善二元动作,确保“不泄露内存”。

针对这个问题,笔者通常的解决方案是内部建立一套登记机制,记录所有在用的内存块,当程序退出时,如果发现还有内存块在内存池中处于激活状态,即表示有内存块忘了释放,内存池会帮助程序员释放内存,避免产生内存泄漏。

7.1.2  不产生碎片

前文我们已经说过,对于7*24小时运行的服务器和嵌入式设备,其对内存的管理要求很高,仅仅不泄露内存是不够的,还必须保证无内存碎片的产生,确保内存池可以长期有效地提供服务。

从前文我们知道,内存碎片的根源,在于一个程序,无序地申请任意大小的内存,最后导致系统堆上的内存不连贯,虽然从统计上得知,还有足够的内存空间,但这是由小块的内存区域组成,没有足够的,整块的大型空间,使后续的大块内存申请无法成功,导致服务无法继续。

这个问题比较难解决,很多解释型高级语言,可以在运行前,主动分析程序,对大型的内存申请实行预分配制度,但是,在CC++里面,由于是编译执行,动态内存申请又过于灵活,很多内存块的尺寸取决与中间计算结果,因此,无法实现预分配,内存的申请和释放动作完全依赖应用程序自己的设计,无法实现统一管控。

笔者经过思考,总结了如下几条推论,以此来试图控制内存碎片的产生:

1、一个应用程序,总的来说,其使用的所有内存,不会超过其目标运行平台的基本内存空间,这很好理解,每个程序员做程序,总是能预估自己的程序,需要多大内存,因此,在产品说明书上,会提示用户准备足够内存的计算机。

2、由上可知,我们在动态内存块可以重用的前提下,可以利用一套机制,屏蔽内存区的动态释放工作,即所有的动态内存,一旦申请,在本次运行期不再释放,这并不会导致计算机内存溢出。

3、动态内存块可以重用,需要保证两点,首先是有一套管理机制,可以记录申请和释放的所有内存块,把释放的内存块二次提供给新的内存申请使用,其次,必须对内存块取模,减小内存块的种类,提高内存块的可重用性。关于取模的原则和方法,我们后文讨论。

这样的话,如果按照上述机制来设计内存池,虽然我们应用程序在运行过程中,存在大量的动态内存申请,但就该程序运行期总的需求来说,使用的最大内存数并没有多大变化,在操作系统看来,这个程序从一开始,申请了差不多的内存之后,就不再申请,全部是内部重用,自然也就没有内存碎片的产生了。

提示:如果计算机系统内存太小,这种机制导致内存溢出了,那是说明应用程序对自己使用的最大内存没有估计准确,用户使用的计算机太低档,正确的解法不是改程序,而是请用户加大内存,或者干脆换更高档的计算机设备。

7.1.3  可以自动报警

在解决“不泄露”的问题时,笔者设计了一个内存管理链表,在设计该链表的时候,笔者突发奇想,由于内存池已经收拢了所有的内存申请和释放行为,那么,我们自然可以很轻易地知道是哪个模块在申请内存,为什么不把这个信息记录下来,帮助Debug呢?

我们知道,内存泄漏问题之所以难以解决,并不是这个问题有多复杂,而是由于内存申请和释放,是程序行为,我们只有在最后的程序运行中,隐约观察到内存使用在不断增长,由此推论可能程序有内存泄漏,但这种观察很不直观,无法帮助程序员精确查找是哪个模块发生了内存泄漏。

但如果我们设计内存池时,要求申请内存的模块,必须注明自己的模块名,在退出时,一旦检测出哪个模块忘了释放内存,马上将其申请信息打印出来,不就可以帮助程序员很方便地检查出发生内存泄漏的模块吗?顺便,也就能在开发期快速解决掉所有的内存泄漏,彻底杜绝这类bug

经过思考,笔者的内存池所有的内存申请动作,全部改为如下格式:

MemPool.Malloc(int nSize,char* szInfo);

 

这明显有别于C语言原有的内存申请函数:

malloc(int nSize);

 

这里的szInfo,是笔者规定的124Bytes的说明性文字,强迫所有申请内存的模块,必须在其中声明自身的身份,一旦发生泄漏,任何一次运行完毕,内存池析构时,立即会打印出相关的信息,程序员即可实现快速查找。

经过试用,这样的效果非常明显,任何一段程序,只要忘了释放内存,则在第一运行结束时,内存池会自动打印报警信息,信息中标明是哪个模块的哪个函数,由于什么原因分配的内存块忘了释放,程序员几乎立即就可以找到故障点,排除bug

提示:笔者在前不久带领团队开发的一个服务器集群中,由于引入了这类注册+自报警机制,所有的CC++程序模块,从未出现内存泄漏,极大地提升了程序的稳定性,也为项目的顺利完成打下了坚实的基础。

 

本文转自 tonyxiaohome51CTO博客,原文链接: http://blog.51cto.com/tonyxiaohome/595351,如需转载请自行联系原作者

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值