目录
一、内存泄漏
1、内存泄漏的定义
内存泄漏(Memory Leak)是编程中的一个常见问题,特别是在使用如C或C++这类需要手动管理内存的语言时。它指的是程序中已分配的内存由于某种原因程序未释放或无法释放,导致系统无法再次使用这部分内存。随着时间的推移,内存泄漏可能会导致程序占用的内存越来越多,最终可能耗尽系统资源,影响程序的性能,甚至导致程序崩溃。
2、如何检测内存泄漏
当我们只运行程序,不会有任何反馈给我们内存泄漏的信息,因此需要我们自己进行寻找。举个简单的例子。
//利用宏定义来使用我们自己定义的函数
#define malloc(size) nmalloc(size)
#define free(ptr) nfree(ptr)
void *nmalloc(size_t size)
{
void * ptr = malloc(size);
printf("malloc\n"); //每次开辟都要输出信息
return ptr;
}
void nfree(void * ptr)
{
free(ptr);
printf("free\n"); //每次free掉都要打印信息
}
int main()
{
size_t size =5;
void * p1 = malloc(size);
void * p2 = malloc(size * 2);
void * p3 = malloc(size * 3);
free(p1);
free(p3);
}
//当我们通过这个简单的例子可以看出,哪个没有被释放掉
二、内存泄漏检测的实现(宏定义版)
对于上面的简单例子,我们可以得知,如果我们将每次开辟空间的信息存放起来,将他们开辟的空间地址放到文件中,然后每次释放内存的时候都将这个文件信息进行删除操作。这样我们就可以得到哪个内存没有被释放。
具体实现不难,我们主要是将这块内存,他的地址在哪,他在哪个函数中开辟的,他在程序中是第几行,他的大小是多少,主要是讲这些信息存放起来。
//利用宏定义来使用我们自己定义的函数
#define malloc(size) nmalloc(size)
#define free(ptr) nfree(ptr)
void *nmalloc(size_t size,const char * filename,const char * funcname,int lins)
{
void * ptr = malloc(size);
char buff[128]={0};
snprintf(buff,128,"./bloc/%p.mem",ptr); //首先创建一个目录,然后以这个信息为名称作为文件放入到目录中去。
FILE * fp = fopen(buff,"w");
if(!fp)
{
free(ptr); //如果开辟失败就free掉
return NULL;
}
//下面就是存放具体的信息,返回所在函数,具体行数等等,通过这些我们就可以定位内存泄漏的位置
fprintf(fp,"[+] malloc [%s:%s:%d] %p,size = %ld\n",filename,funcname,lins,ptr,size);
fflush(fp); //write
fclose(fp);
return ptr;
}
void nfree(void * ptr,const char * filename,const char * funcname,int lins)
{
char buff[128]={0};
snprintf(buff,128,"./bloc/%p.mem",ptr);
if(unlink(buff)>0) //通过搜寻,并且删除的操作
{
printf("double free: %p\n", ptr);
return ;
}
return free(ptr);
}
三、内存泄漏检测的实现(函数指针版)
当然我们可以使用函数指针实现,并且具体的操作与宏定义大都相同,一些细节需要注意。
首先我们要重写malloc这个函数,让系统调用我们的malloc,然后通过函数指针指向系统的malloc(会在后面列出)。
但是此时有个情况发生,我们通过malloc来调用这个函数的时候,会经过printf这个函数,但是当系统第一次调用printf的时候会在printf内部中通过调用malloc来开辟一块内存,这样就成递归操作了,程序会挂掉。
void * malloc(size_t size)
{
printf("malloc");
}
int main()
{
void * p1 = malloc(5);
void * p2 = malloc(5);
void * p3 = malloc(5);
}
//如果程序正常运行的话 p1的地址是0100
//然而调用一次printf后 p2的地址为4300
//但是p3的地址为4400,也就是说p2和p3之间是连续的,但是p1和p2之间并不是
//因为其中包含了printf的一次malloc
因此我们需要通过自己来判断是否为第一次调用printf,避免成为递归。通过int enable_malloc = 1;来判断,当然free中也是相同情况。下面只列举malloc。
int enable_malloc = 1;
void * malloc(size_t size)
{
if(enable_malloc) //这里为1
{
enable_malloc = 0; //设置为0
ptr = mallock_f(size); //开辟所需要的空间
printf("ptr:%p\n",ptr); //然后第一次调用printf,他的内部会再走一边这个malloc,但是这里的enable_malloc为0,会走else的函数。
enable_malloc = 1; //最后恢复
}
else //当enable_malloc 为0 会走这里
{
ptr = mallock_f(size); //然后为printf开辟内存
//printf("ptr:%p\n",ptr);
}
return ptr;
}
下面就是全部的代码,和宏定义版几乎没有任何区别,也就是多了判断。但是其中还有个新东西:__builtin_return_address。这是为了得到当前函数返回地址,即此函数被别的函数调用,然后此函数执行完毕后,返回,所谓返回地址就是那时候的地址。
举例:现在main中调用f1函数,f1中调用f2函数,那么在f2中使用__builtin_return_address(0),那么返回的地址是f2的上一层f1的地址。
int enable_malloc = 1;
int enable_free = 1;
//设置函数指针
typedef void *(*mallock_t)(size_t size);
typedef void (*free_t)(void * ptr);
mallock_t mallock_f = NULL;
free_t free_f = NULL;
void * malloc(size_t size)
{
if(!mallock_f)
{
mallock_f = dlsym(RTLD_NEXT,"malloc"); //通过dlsym将回调函数设置为系统调用的malloc
}
void * ptr = NULL;
if(enable_malloc)
{
enable_malloc = 0;
ptr = mallock_f(size);
//printf("ptr:%p\n",ptr);
//得到当前函数返回地址,即此函数被别的函数调用,然后此函数执行完毕后,返回,所谓返回地址就是那时候的地址。
void *caller = __builtin_return_address(0); //用于返回上几级函数:main->f1()->f2()->f3() 在f3里面使用 0 这个,那么返回上面的f2
char buff[128]={0};
snprintf(buff,128,"./bloc/%p.mem",ptr);
FILE * fp = fopen(buff,"w");
if(!fp)
{
free(ptr);
return NULL;
}
fprintf(fp,"[+] malloc [%p] %p,size = %ld\n",caller,ptr,size);
fflush(fp); //write
fclose(fp);
//printf("malloc\n");
enable_malloc = 1;
}
else
{
ptr = mallock_f(size);
//printf("ptr:%p\n",ptr);
}
return ptr;
}
void free(void * ptr)
{
if(!free_f)
{
free_f = dlsym(RTLD_NEXT,"free");
}
char buff[128]={0};
snprintf(buff,128,"./bloc/%p.mem",ptr);
if(unlink(buff)>0) //delete
{
printf("double free: %p\n", ptr);
return ;
}
//printf("free\n");
return free_f(ptr);
}
四、一些问题
1、线上内存泄漏怎么办
我们可以通过实现内存泄漏模块,通过类似于nginx的热更新操作,然后定位内存泄漏的位置。将问题解决之后,在夜深人静的时候将项目重新启动。
2、malloc都有日志,这样性能是不是会很低
我们可以每隔一段时间输出一次,不用一直输出,这样选择性打开,可以提高性能。
内存泄漏的代码就这一些,感谢大家观看!https://xxetb.xetslk.com/s/2D96kH