如何定位内存泄漏
关注公众号【高性能架构探索】,第一时间获取干货;回复【pdf】,免费获取计算机经典资料
本文节选自公众号文章:内存泄漏-原因、避免以及定位
在发现程序存在内存泄漏后,往往需要定位泄漏点,而定位这一步往往是最困难的,所以经常为了定位泄漏点,采取各种各样的方案,甭管方案优雅与否,毕竟管他白猫黑猫,抓住老鼠才是好猫
,所以在本节,简单说下笔者这么多年定位泄漏点的方案,有些比较邪门歪道,您就随便看看就行?。
日志
这种方案的核心思想,就是在每次分配内存的时候,打印指针地址,在释放内存的时候,打印内存地址,这样在程序结束的时候,通过分配和释放的差,如果分配的条数大于释放的条数,那么基本就能确定程序存在内存泄漏,然后根据日志进行详细分析和定位。
char * fun() {
char *p = (char*)malloc(20);
printf("%s, %d, address is: %p", __FILE__, __LINE__, p);
// do sth
return p;
}
int main() {
fun();
return 0;
}
统计
统计方案可以理解为日志方案的一种特殊实现,其主要原理是在分配的时候,统计分配次数,在释放的时候,则是统计释放的次数,这样在程序结束前判断这俩值是否一致,就能判断出是否存在内存泄漏。
此方法可帮助跟踪已分配内存的状态。为了实现这个方案,需要创建三个自定义函数,一个用于内存分配,第二个用于内存释放,最后一个用于检查内存泄漏。代码如下:
static unsigned int allocated = 0;
static unsigned int deallocated = 0;
void *Memory_Allocate (size_t size)
{
void *ptr = NULL;
ptr = malloc(size);
if (NULL != ptr) {
++allocated;
} else {
//Log error
}
return ptr;
}
void Memory_Deallocate (void *ptr) {
if(pvHandle != NULL) {
free(ptr);
++deallocated;
}
}
int Check_Memory_Leak(void) {
int ret = 0;
if (allocated != deallocated) {
//Log error
ret = MEMORY_LEAK;
} else {
ret = OK;
}
return ret;
}
工具
在Linux上比较常用的内存泄漏检测工具是valgrind
,所以咱们就以valgrind为工具,进行检测。
我们首先看一段代码:
#include <stdlib.h>
void func (void){
char *buff = (char*)malloc(10);
}
int main (void){
func(); // 产生内存泄漏
return 0;
}
- 通过
gcc -g leak.c -o leak
命令进行编译 - 执行
valgrind --leak-check=full ./leak
在上述的命令执行后,会输出如下:
==9652== Memcheck, a memory error detector
==9652== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9652== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==9652== Command: ./leak
==9652==
==9652==
==9652== HEAP SUMMARY:
==9652== in use at exit: 10 bytes in 1 blocks
==9652== total heap usage: 1 allocs, 0 frees, 10 bytes allocated
==9652==
==9652== 10 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9652== at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==9652== by 0x40052E: func (leak.c:4)
==9652== by 0x40053D: main (leak.c:8)
==9652==
==9652== LEAK SUMMARY:
==9652== definitely lost: 10 bytes in 1 blocks
==9652== indirectly lost: 0 bytes in 0 blocks
==9652== possibly lost: 0 bytes in 0 blocks
==9652== still reachable: 0 bytes in 0 blocks
==9652== suppressed: 0 bytes in 0 blocks
==9652==
==9652== For lists of detected and suppressed errors, rerun with: -s
==9652== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
valgrind的检测信息将内存泄漏分为如下几类:
- definitely lost:确定产生内存泄漏
- indirectly lost:间接产生内存泄漏
- possibly lost:可能存在内存泄漏
- still reachable:即使在程序结束时候,仍然有指针在指向该块内存,常见于全局变量
主要上面输出的下面几句:
==9652== by 0x40052E: func (leak.c:4)
==9652== by 0x40053D: main (leak.c:8)
提示在main函数(leak.c的第8行)fun函数(leak.c的第四行)产生了内存泄漏,通过分析代码,原因定位,问题解决。
valgrind不仅可以检测内存泄漏,还有其他很强大的功能,由于本文以内存泄漏为主,所以其他的功能就不在此赘述了,有兴趣的可以通过valgrind --help
来进行查看
对于Windows下的内存泄漏检测工具,笔者推荐一款轻量级功能却非常强大的工具
UMDH
,笔者在十二年前,曾经在某外企负责内存泄漏,代码量几百万行,光编译就需要两个小时,尝试了各种工具(免费的和收费的),最终发现了UMDH,如果你在Windows上进行开发,强烈推荐。
经验之谈
在C/C++开发过程中,内存泄漏是一个非常常见的问题,其影响相对来说远低于coredump等,所以遇到内存泄漏的时候,不用过于着急,大不了重启嘛?。
在开发过程中遵守下面的规则,基本能90+%避免内存泄漏:
- 良好的编程习惯,只有有malloc/new,就得有free/delete
- 尽可能的使用智能指针,智能指针就是为了解决内存泄漏而产生
- 使用log进行记录
- 也是最重要的一点,
谁申请,谁释放
对于malloc分配内存,分配失败的时候返回值为NULL,此时程序可以直接退出了,而对于new进行内存分配,其分配失败的时候,是抛出std::bad_alloc
,所以为了第一时间发现问题,不要对new异常进行catch,毕竟内存都分配失败了,程序也没有运行的必要了。
如果我们上线后,发现程序存在内存泄漏,如果不严重的话,可以先暂时不管线上,同时进行排查定位;如果线上泄漏比较严重,那么第一时间根据实际情况来决定是否回滚。在定位问题点的时候,可以采用缩小范围法
,着重分析这次新增的代码,这样能够有效缩短问题解决的时间。