要解决内存泄漏,首先面对的问题就是,如何判断一个程序内存泄露了呢?因为不可能每个内存申请释放的程序都像上面所举的例子那样直观明了。实际上我们面对的程序要比例子复杂千倍万倍。有可能在A函数里申请的内存,在B函数甚至在另外一个.c
文件里才会去释放,中间可能经历了无数的if...else
,有些分支可能提前return
,有些分支又调用了其他的函数,整个系统盘根错节,想靠肉眼去从代码里发现内存泄露的蛛丝马迹,捋明白一块内存的前世今生,简直难于登天。我们不排除有少数基础扎实的资深C语言专家的确有肉眼在代码里察觉到内存泄漏的能力,但对于更多的人来说,这可能并不是一个简单高效的方法。
如果一个程序在运行的过程中,可以看到其占用的内存在不断的增加,尽管有时候增加得很慢,但这时候就千万要小心了,因为你的程序很有可能存在着内存泄漏。
可如果程序比较简单,例如上面的第二段代码,程序一执行就结束了,根本来不及观察内存的变化情况,是无法通过观测正在 运行的程序内存增长情况来判断是否有内存泄漏的。这时候,我们可能要借助一些其他的调试手段或者第三方工具,来判断是否有内存泄漏,比如著名的valgrind
,我们将在下一个章节重点介绍。
当然,对于某些引入内存池技术的程序,对于内存泄漏还有另外一层注解。由于内存池具有减少内存碎片,减少与 内核态交互次数等特点,申请和释放都是针对池子而言,不太容易出现内存泄漏,因此被广泛应用于很多企业级的应用程序中。其中,内存池的典型实现,包括定长内存池LOKI
、BOOST
,不定长内存池apr_pool
、obstack
等。
当内存池化之后,由于对于内存的申请和销毁都是先由内存池初始化时向系统申请一块内存,在程序结束时销毁该内存池,中间所有关于内存的操作都是针对池子而言的,因此不太容易出现内存泄漏。但这也并不能彻底解决内存泄漏的隐患。如果某个程序是长期运行的,那么可能申请的内存一直存在于池子中,不会得到销毁,定长内存池相对好一点 ,特别是对于非定长的内存池,如果在程序中一味地从池子中申请内存,但并没有在适当的地方及时归还给池子,同样会造成内存越用越大,导致系统内存耗尽。这种问题相对来说更危险:因为通过valgrind
之类的工具是无法排查出来的。
从内存泄漏的现象和危害性来说,我们不难发现:内存泄漏的影响不像 coredump
之类有立竿见影的直观影响,它的影响可能是缓慢的,要运行很长一段时间才会出现,可一旦出现,就是非常致命的危险。因此,也很容易知道,对于一个并非长期运行的程序而言 ,可能瞬间就执行完了,即便程序中有内存泄露,但影响却是微乎其微的,因为程序一结束,系统就会将所有内存都回收了。 但即便如此,从良好的代码习惯和代码洁癖的角度来看,仍然是不建议这种情况出现,因为谁也保不准你写的这段代码会被谁复用,从而造成非常严重的事故。
所以大致总结一下,我们判断一个程序是否出现内存泄漏,主要的依据有以下几个方面:
- 借助第三方工具或
hook
技术,动态或静态观测内存的申请和释放情况 - 对于正在运行的程序,可观测内存的增长情况,在数据量级恒定的情况下,如果出现长时间的线性增长,则有可能出现了内存泄漏的情况
- 梳理代码里申请内存变量的生命周期(俗称人工跑代码),对于比较复杂的程序,要求C语言功底相对较深
- 基于
gperftools
之类的pprof
观测工具,追踪内存的生命周期
本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对C/C++课程感兴趣的读者,可以点击链接,查看详细的服务:C/C++Linux服务器开发/高级架构师