确认内存泄漏以及定位手段

Posted by Dandan on November 10, 2022

前言

作为一个C程序员,那么内存泄漏不可避免,当排查自己的代码还好一些,但是排查起来别人的“狮山”或者一些开源代码,常常会让自己非常的崩溃,今天总结一下如何确认程序确实发生了内存泄漏以及定位手段。

确认内存泄漏手段

借助工具

LINUX平台中最出名的内存检测工具:Valgrind, 它会生成非常详细的报告,包括泄露的位置和分配的内存大小:

valgrind --leak-check=full ./your_program

观察内核数据

程序运行时发生内存泄漏会变的非常缓慢且占用很多的内存。
例如一个程序进程是4268, 通过命令cat /proc/4268/status 查看内存:

VmSize:	  虚拟内存
VmRSS:	  物理内存
RSS:      是常驻内存集(Resident Set Size),表示该进程分配的内存大小。
RSS:      不包括进入交换分区的内存。
RSS:      包括共享库占用的内存(只要共享库在内存中)
RSS:      包括所有分配的栈内存和堆内存。
VSZ:      表示进程分配的虚拟内存。
VSZ:      包括进程可以访问的所有内存,包括进入交换分区的内容,以及共享库占用的内存。

隔一段时间查看所占内存是否持续有增加,如果有增加证明有内存释放。
或者通过top命令查看进程所占的内存。
当然,该方法不太准确,具体要根据程序的情况去判断,例如程序应用确实申请了内存,还没到释放的时候,那么观察到内存有增加,是正常的。

编写一个测试用例

创建一个测试用例,通过模拟不同的情况,观察是否会导致内存不断增加。

定位手段

借助工具

同样的,Valgrind 是常见的定位内存泄漏工具,其他的还有GDB、Xcode Instruments、Purify等。

观察

手动检查代码确认内存泄漏,虽然不如工具直观,方式如下:

  • 在每次分配内存后,请确保有对应的释放操作(如free或delete)。检查代码中是否有忘记释放内存的地方。

  • 使用计数器来跟踪内存分配和释放的次数。在每次分配时增加计数器,在每次释放时减少计数器。如果计数器最终不为零,那么可能存在内存泄漏。

  • 在程序退出前,检查所有应该被释放的内存是否都被释放。这可以通过编写一个清理函数来实现。

  • 追踪指针:跟踪指向已释放内存的指针,这可能是悬挂指针的一个来源。在释放内存后,将指针设置为NULL,以防止意外访问。

  • 循环引用:如果你使用了复杂的数据结构,如链表或树,确保没有循环引用导致内存无法被释放。这可能需要仔细检查你的数据结构和释放逻辑。

代码优化

  • 内存分配错误检查:确保内存分配操作成功,否则可能会导致未初始化的内存泄漏。在调用malloc或类似函数后,检查返回值是否为NULL。

  • 内存释放位置:确认内存释放的位置在正确的地方,不要在循环中或不恰当的条件下释放内存。释放内存后,避免再次引用该内存。

  • 记录内存分配和释放:添加日志或打印语句来跟踪内存分配和释放的情况,以便更容易定位问题。

  • 内存池:考虑使用内存池来管理内存分配,以减少碎片和泄漏的风险。