以前写的一些程序运行一段时间后占用的内存越来越多,估计是内存泄露了。服务端的程序要长时间的运行,内存泄露是个很严重的问题。于是再检查程序,很崩溃的是还有另外一个模块不是自己写的,看起来很麻烦。看了半小时后发现一些问题,但是还是不能保证是否完全解决了。同事让我用以前他们写的一些函数,对应的为MALLOC和FREE。仔细看了一下觉得很不错,其实就是把malloc和free函数封装了一下,用来记录申请空间的文件和代码位置,使用方法就是用MALLOC和FREE替代原来的函数。主要的数据结构是:
typedef struct
{
long pcode; //指针
char filename[128]; //申请空间的源文件名称
int line; //申请空间的代码所在的行
int ct; //内存状态: 0-未闭合,1-闭合,2-log/脚手架
}mem_info;
mem_info mem_in[MEM_SIZE]; //MEM_SIZE最大指针数目
int mem_in_id; //数组中已经占有的mem_info数目
int mem_check_statue; //是否进行内存泄露检查
然后有两个函数,一个是初始化函数mem_check_init(),另一个为mem_check_write(),这样就能检查者两个函数之间的代码是否有内存泄露,mem_check_write()可以打印成一个表,所有申请了空间的代码的文件名称和代码所在的行数,以及运行到mem_check_write()这里的时候所有申请空间的状态,1表示已经释放,0表示申请未释放,2表示的是脚手架的位置(用来方便检查哪一小段代码是否有内存泄露)。
#define MALLOC(size) __ck_malloc(size,__FILE__,__LINE__) //__FILE__ 文件 __LINE__ 代码所在行
void *__ck_malloc(int size,char *file,int line)
{
void *p=malloc(size);
if (mem_check_statue) return p;
if (mem_in_id>=MEM_SIZE) return p;
mem_in[mem_in_id].pcode=(long)p;
strcpy(mem_in[mem_in_id].filename,file);
mem_in[mem_in_id].line=line;
mem_in[mem_in_id].ct=0; // 状态: 0-未闭合
mem_in_id++;
return p;
}
那么FREE(p),进行的操作就是现在数组中找到是否有这个p,如果有就改变状态,变为1表示闭合了,也就是释放掉了。CALLOC和MALLOC类似,是调用calloc,函数malloc()和函数calloc()的主要区别是前者不能初始化所分配的内存空间,而后者能。REALLOC有点不一样,调用void* np=realloc(p,size),这里要注意np和原来的p有可能不一样,有可能一样,比较一下进行相应处理。最后mem_check_write()遍历上上面的数组打印出来表,其顺序就是按照代码执行的顺序了,其中脚手架可以比较方便的定位于申请了没有释放的代码行,也就是查找两个2之间的0所对应的行。
这是一个很不错的方法,今天用这个办法找到了好多处不易发现的内存泄露错误。但这也有其缺点,即使完全通过也不能保证就完全没内存泄露了,除非测试时运行代码的覆盖率要保证所有代码都运行到了,这也是正规的、高质量的测试所要做到的程度。我们现在没有时间来做足够好的测试,以后再好好规范一下。