成熟的内存泄漏检测工具很多,比如:
valgrind https://www.valgrind.org/
mtrace https://man7.org/linux/man-pages/man3/mtrace.3.html (glibc内置)
memwatch https://www.linkdata.se/sourcecode/memwatch/
dlmalloc 一种内存分配器,比如android中 bionic c库使用dlmalloc 来分配内存
leakTracer http://www.andreasen.org/LeakTracer/
基本上原理都是两种情况:
一种是对申请内存的系统函数malloc free 重写/宏定义替换/注册hook等手段,达到每次执行 malloc free等函数是都做一个记录,最后通过记录来查看分析是否内存没有释放,重复释放,溢出等等问题。 这种方式需要在编译前对我们的代码进行改动,编译出来的文件就已经具备这些检测功能,比如上面的 mtrace memwatch dlmalloc 等。
另一种方式,不需要对原执行文件或库进行改动,而是虚拟出一个假的运行环境,这个环境中提供系统的这些功能,把我们可执行文件放到这个环境中运行,其运行的过程都被这个虚拟环境所记录,利用这个虚拟环境的记录来分析内存使用情况。比如valgrind
以上都是动态的检测,即监控记录程序运行的过程,也可以静态检测,就是通过一个语法从代码层面文字分析。
这里用第一种原理,类似于memwatch,简单实现一个malloc free的检测模块,当然实际的开发中会使用以上成熟的检测工具,这里做这么一个小工具,主要是探究其原理,毕竟这些成熟的检测工具,也不是生来就有的,也是前辈们一行行代码垒起来的。
=========================================================================
分割线
=========================================================================
主要原理:
用宏定义对malloc free进行一层包装,在里面维护一个列表(这里对应一个全局的大数组),记录每一次的 malloc和free调用。
实际效果:
//canok 20210619
#ifndef D_MALLOC_H
#define D_MALLOC_H
#define DMALLOC_ENABLE 1
void *D_malloc(size_t size,const char *file, int line);
void *D_calloc(size_t nmemb, size_t size,const char *file, int line);
void *D_realloc(void *ptr, size_t size,const char *file, int line);
void D_free(void *pMemory, const char *file, int line);
#if DMALLOC_ENABLE
#define D_Malloc(size) D_malloc(size,__FILE__,__LINE__)
#define D_Calloc(nmeb,size) D_calloc(nmeb,size,__FILE__,__LINE__)
#define D_Realloc(ptr,size) D_realloc(ptr,size,__FILE__,__LINE__)
#define D_Free(pMemory) D_free(pMemory,__FILE__,__LINE__)
#else
#define D_Malloc(size) malloc(size)
#define D_Calloc(nmeb,size) calloc(nmeb,size)
#define D_Realloc(ptr,size) realloc(ptr,size)
#define D_Free(pMemory) free(pMemory)
#endif
void Debug_malloc_start();
void Debug_malloc_stop();
#endif
//canok 20210619
#include <stdlib.h>
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include "D_malloc.h"
typedef struct _s_malloc_type{
const void *pointer;
unsigned int bytes;
const char *file;
unsigned int line;
}DMallocType;
#define INFO_SIZE 1024*10
#define SIZE_OF_MEM_DEBUG_ARRAY 1000
#define ALOG(x...) printf("[%s %s %d]:",__FILE__,__FUNCTION__,__LINE__);printf(x)
static DMallocType MallocTable[SIZE_OF_MEM_DEBUG_ARRAY] = {0};
static pthread_mutex_t gMutex = PTHREAD_MUTEX_INITIALIZER;
static int gStart =0;
static char *gInfo=NULL;
static void MM_MallocAddEntry(const void *pMemory,unsigned int size,const char *file,unsigned int line){
int i=0;
if( pMemory != NULL ){
for( i = 0; i < SIZE_OF_MEM_DEBUG_ARRAY; i++ ){
if( MallocTable[i].pointer == NULL ){
MallocTable[i].pointer = pMemory;
MallocTable[i].bytes = size;
MallocTable[i].file = file;
MallocTable[i].line = line;
break;
}
}
if( i >= SIZE_OF_MEM_DEBUG_ARRAY ){
ALOG("VIDEO_Memory Unable to store malloc info: [%s:%d] %d bytes\n",file, line, size );
}
}
}
static DMallocType* MM_MallocFindEntry(const void *pMemory){
unsigned int i=0;
if( pMemory != NULL ){
for( i = 0; i < SIZE_OF_MEM_DEBUG_ARRAY; i++ ){
if( MallocTable[i].pointer == pMemory ){
return &MallocTable[i];
}
}
}
return NULL;
}
static void MM_MallocDeleteEntry(DMallocType *pEntry){
if( pEntry != NULL ){
memset( pEntry, 0, sizeof(*pEntry) );
}
}
static void checkTable(){
int i=0;
for( i = 0; i < SIZE_OF_MEM_DEBUG_ARRAY; i++ ){
if( MallocTable[i].pointer !=NULL ){
sprintf(gInfo,"%s not free ptr=%p , %d bytes at: %s:%d\n",gInfo,MallocTable[i].pointer,MallocTable[i].bytes,MallocTable[i].file,MallocTable[i].line);
//snprintf(gInfo,INFO_SIZE,"%s not free ptr=%p , %d bytes at: %s:%d",gInfo,MallocTable[i].pointer,MallocTable[i].bytes,MallocTable[i].file,MallocTable[i].line);
}
}
}
void *D_malloc(size_t size,const char *file, int line){
void *pMemory = NULL;
pthread_mutex_lock(&gMutex);
do{
pMemory = malloc(size);
if(gStart && pMemory){
//be careful, May be AddEntry failded
MM_MallocAddEntry(pMemory,size,file,line);
}
}while(0);
pthread_mutex_unlock(&gMutex);
return pMemory;
}
void * D_calloc(size_t nElem, size_t elSize, const char *file, int line){
void *pMemory = NULL;
pthread_mutex_lock(&gMutex);
do{
pMemory = calloc(nElem,elSize);
if(gStart && pMemory){
//be careful, May be AddEntry failded
MM_MallocAddEntry(pMemory,elSize*nElem,file,line);
}
}while(0);
pthread_mutex_unlock(&gMutex);
return pMemory;
}
void * D_realloc(void *pOldMemory , size_t size, const char *file, int line){
void *pMemory = NULL;
pthread_mutex_lock(&gMutex);
do{
if(gStart){
DMallocType *pEntry=NULL;
pEntry = MM_MallocFindEntry(pOldMemory);
if(NULL == pEntry){
ALOG("findEntyr erro[%s:%d] ptr=%p, %lu bytes",file, line, pOldMemory, size);
break;
}
pMemory = realloc(pOldMemory,size);
if(pMemory){
//be careful, May be AddEntry failded
MM_MallocDeleteEntry(pEntry);
MM_MallocAddEntry(pMemory,size,file,line);
}
}else{
pMemory = realloc(pOldMemory,size);
}
}while(0);
pthread_mutex_unlock(&gMutex);
return pMemory;
}
void D_free(void *pMemory, const char *file, int line){
DMallocType *pEntry=NULL;
pthread_mutex_lock(&gMutex);
do{
if(gStart){
pEntry = MM_MallocFindEntry(pMemory);
if(NULL == pEntry){
sprintf(gInfo,"%s free invalue ptr:=%p at %s:%d\n",gInfo,pMemory,file,line);
//snprintf(gInfo,INFO_SIZE,"%s free invalue ptr:=%p at %s:%d\n",gInfo,pMemory,file,line);
break;
}else{
MM_MallocDeleteEntry(pEntry);
free(pMemory);
}
}else{
free(pMemory);
}
}while(0);
pthread_mutex_unlock(&gMutex);
/* Error checking */
}
void Debug_malloc_start(){
pthread_mutex_lock(&gMutex);
do{
if(gStart){
break;
}
gInfo = (char *)malloc(INFO_SIZE);
if(gInfo==NULL){
ALOG("[%s%d] start failed, malloc err",__FUNCTION__,__LINE__);
break;
}
gStart=1;
}while(0);
pthread_mutex_unlock(&gMutex);
}
void Debug_malloc_stop(){
pthread_mutex_lock(&gMutex);
do{
if(!gStart){
break;
}
gStart=0;
checkTable();
FILE*fpout = fopen("loginfo","w+");
if(fpout!=NULL){
fwrite(gInfo,1,INFO_SIZE,fpout);
fclose(fpout);
}
ALOG("the memory info:\n%s",gInfo);
free(gInfo);
}while(0);
pthread_mutex_unlock(&gMutex);
}
int main(){
printf("hello world!\n");
Debug_malloc_start();
char *m1 = D_Malloc(128);
char *m2 = D_Malloc(129);
char *m3 = D_Calloc(50,2);
char *m4 = D_Realloc(m2,200);
char *m5=NULL;
D_Free(m5);
Debug_malloc_stop();
}