当前位置: 首页 > news >正文

怎样检测内存泄露

http://3xin2yi.info/wwwroot/tech/doku.php/tech:system:memoryleak

内存泄露是指在程序运行过程中,动态申请了部分内存空间,却没有在使用完毕后将其释放,结果导致该内存空间无法被再次使用。内存泄露是使用C或C++编程时易犯的错误之一,严重的内存泄露常常表现为:程序运行时间的越长,占用的内存越多,最终导致系统内存枯竭。

如以下代码:

int *dup_buffer(int* buffer, int size)
{
    int *p;
 
    p = (int *) malloc(size*sizeof(int));
    if (p !=0)
    {
        memcpy(p, buffer, size);
    }
 
    return p;
}

如果程序调用该函数拷贝内存却没有在函数外部释放返回的指针,则该程序发生了内存泄露。

malloc提供的钩子函数

使用GLIBC进行内存管理时可以利用malloc提供的钩子函数进行内存分配跟踪,从而发现内存泄露。

以下代码可以作为一个桩子嵌入应用程序来检查内存泄露:

/* Prototypes for __malloc_hook, __free_hook */
#include <malloc.h>
#include <pthread.h>
#include <stdio.h>
 
#ifndef MEMCHK_LIST_SIZE
#define MEMCHK_LIST_SIZE 1000
#endif
 
typedef struct memstat
{
    unsigned int addr;
    unsigned int size;
    unsigned int caller;
    struct memstat *next;
} memstat_t;
 
memstat_t mem_list[MEMCHK_LIST_SIZE+2];
 
/* Globals */
void *(*old_malloc_hook)(size_t);
void (*old_free_hook)(void *);
memstat_t *listhead_free = &mem_list[0];
memstat_t *listhead_mem = &mem_list[MEMCHK_LIST_SIZE+1];
pthread_mutex_t hook_locker = PTHREAD_MUTEX_INITIALIZER;
 
void dump_malloc();
 
static memstat_t *remove_from_list(memstat_t *head, unsigned int addr)
{
    memstat_t *p1 = head;
    memstat_t *p2 = p1->next;
 
    while (p2 != 0)
    {
    if (p2->addr == addr)
        break;
	p1 = p2;
	p2 = p2->next;
    }
 
    if (p2 != 0) /* found */
    {
        p1->next = p2->next;
    }
 
    return p2;
}
 
static void add_to_list(memstat_t *head, memstat_t *p)
{
    p->next = head->next;
    head->next = p;
}	 
 
static void print_list(memstat_t *head)
{
    memstat_t *p = head;
    int i = 0;
 
    while (p->next != 0)
    {
        printf("No.%d\taddr=0x%x\tsize=%d\tcaller=0x%x\n", i++, p->next->addr, p->next->size, p->next->caller);
        p = p->next;
    }
    printf("\n");
}
 
void dump_malloc()
{
    if (pthread_mutex_lock(&hook_locker))
        return;
 
    print_list(listhead_mem);
 
    pthread_mutex_unlock(&hook_locker);
}
 
/* Prototypes for our hooks.  */
static void my_init_hook (void);
static void *my_malloc_hook (size_t, const void *);
static void my_free_hook (void*, const void *);
 
/* Override initializing hook from the C library. */
void (*__malloc_initialize_hook) (void) = my_init_hook;
 
static void my_init_hook (void)
{
    int i;
 
    old_malloc_hook = __malloc_hook;
    old_free_hook = __free_hook;
    __malloc_hook = my_malloc_hook;
    __free_hook = my_free_hook;
 
    /* first element for free*/
    listhead_free = &mem_list[0];
    /* link free list */
    for (i = 0; i < MEMCHK_LIST_SIZE; i++)
    {
        mem_list[i].addr = 0;			 
 	mem_list[i].size = 0;			
	mem_list[i].caller = 0;		
	mem_list[i].next = &mem_list[i+1];
    }
    mem_list[MEMCHK_LIST_SIZE].addr = 0;
    mem_list[MEMCHK_LIST_SIZE].size = 0;
    mem_list[MEMCHK_LIST_SIZE].caller = 0;
    mem_list[MEMCHK_LIST_SIZE].next = 0;	
 
    /* last element for busy */
    listhead_mem = &mem_list[MEMCHK_LIST_SIZE+1];
    /* clean busy list */
    listhead_mem->addr = 0;
    listhead_mem->size = 0;
    listhead_mem->caller = 0;
    listhead_mem->next = 0;
}
 
static void *my_malloc_hook (size_t size, const void *caller)
{
    void *result;
    memstat_t *p;
 
    if (pthread_mutex_lock(&hook_locker))
        return 0;
 
    /* Restore all old hooks */
    __malloc_hook = old_malloc_hook;
    __free_hook = old_free_hook;
    /* Call recursively */
    result = malloc (size);
    /* Save underlying hooks */
    old_malloc_hook = __malloc_hook;
    old_free_hook = __free_hook;
 
    p = remove_from_list(listhead_free, 0);
    if (p == 0)
    {
        pthread_mutex_unlock(&hook_locker);
        return 0;
    }
    p->addr = (unsigned int)result;
    p->size = size;
    p->caller = (unsigned int)caller;
    p->next = 0;
    add_to_list(listhead_mem, p);
 
    /* Restore our own hooks */
    __malloc_hook = my_malloc_hook;
    __free_hook = my_free_hook;
 
    pthread_mutex_unlock(&hook_locker);
 
    return result;
}
 
static void my_free_hook (void *ptr, const void *caller)
{
    memstat_t *p;
 
    if (pthread_mutex_lock(&hook_locker))
        return;
 
    /* Restore all old hooks */
    __malloc_hook = old_malloc_hook;
    __free_hook = old_free_hook;
    /* Call recursively */
    free (ptr);
    /* Save underlying hooks */
    old_malloc_hook = __malloc_hook;
    old_free_hook = __free_hook;
 
    /* Restore our own hooks */
    __malloc_hook = my_malloc_hook;
    __free_hook = my_free_hook;
 
    p = remove_from_list(listhead_mem, (unsigned int)ptr);
    if (p == 0)
    {
        pthread_mutex_unlock(&hook_locker);
        return;
    }
    p->addr = 0;
    p->size = 0;
    p->caller = 0;
    p->next = 0;
    add_to_list(listhead_free, p);
 
    /* Restore our own hooks */
    __malloc_hook = my_malloc_hook;
    __free_hook = my_free_hook;
 
    pthread_mutex_unlock(&hook_locker);	 
}

将以上代码保存为memchk.c,直接通过include将之引入到被测试的程序中:

#include "memchk.c"
#include <memory.h>
 
int *dup_buffer(int* buffer, int size)
{
    int *p;
 
    p = (int *) malloc(size*sizeof(int));
    if (p !=0)
    {
        memcpy(p, buffer, size);
    }
 
    return p;
}
 
int main(int argc, char** argv)
{
    int src[100];
    int i;
    int *pDes;
 
    for (i = 0; i < 100; i++) src[i] = i;
 
    pDes = dup_buffer(src, 100);
 
    dump_malloc();
 
    return 0;
}

运行结果为:

No.0    addr=0x8dd6008  size=400        caller=0x8d78ee

有些遗憾的是,0x8d78ee并非函数dup_buffer的地址,而是 malloc_hook_ini的地址。但是,如果我们再增加一次对dup_buffer的调用,如下:

#include "memchk.c"
#include <memory.h>
 
int *dup_buffer(int* buffer, int size)
{
    int *p;
 
    p = (int *) malloc(size*sizeof(int));
    if (p !=0)
    {
        memcpy(p, buffer, size);
    }
 
    return p;
}
 
int main(int argc, char** argv)
{
    int src[100];
    int i;
    int *pDes;
 
    for (i = 0; i < 100; i++) src[i] = i;
 
    pDes = dup_buffer(src, 100);
    pDes = dup_buffer(src, 100);
 
    dump_malloc();
 
    return 0;
}

则运行结果为:

No.0    addr=0x83431a0  size=400        caller=0x8048916
No.1    addr=0x8343008  size=400        caller=0x8d78ee

通过使用“add2line -e test 0x8048916”可得到“test2.c:8”的结果,正是malloc被调用的位置。

缺憾

对于单一模块的初期开发阶段,这种方法尚且有效。但若是出于集成阶段,模块数量众多且相互耦合,代码庞大异常,即是捕捉到malloc产生的泄漏也不易定位——可能是一个公共函数引起,而这个公共函数为多个模块所调用。因此,建议在软件模块的初期开发阶段,为本模块定义专门的内存管理函数,模块内所有的内存分配和释放都是用专有函数,禁止malloc和free,这样不仅不需要为malloc挂接钩子函数,也能更准确地给出泄漏处的详细信息。比如:

void *my_malloc(unsigned int size, int line, char* pFunc);
void *my_free(void *ptr, int line, char* pFunc);

mtrace

mtrace是GNU C库中自带的一个内存泄露检测工具,使用方法为:

  1. 包含mcheck.h
  2. 在适当的位置调用mtrace()和muntrace()
  3. 定义环境变量MALLOC_TRACE,这是一个文件名,mtrace会把内存分配信息记录到该文件中,程序运行结束之后,通过运行“mtrace 可执行程序 记录文件”即可查看内存泄露情况。

依旧是上面的例子代码:

#include <mcheck.h>
#include <memory.h>
 
int *dup_buffer(int* buffer, int size)
{
    int *p;
 
    p = (int *) malloc(size*sizeof(int));
    if (p !=0)
    {
        memcpy(p, buffer, size);
    }
 
    return p;
}
 
int main(int argc, char** argv)
{
    int src[100];
    int i;
    int *pDes;
 
    mtrace();
 
    for (i = 0; i < 100; i++) src[i] = i;
 
    pDes = dup_buffer(src, 100);
    pDes = dup_buffer(src, 100);
 
    muntrace();
 
    return 0;
}

运行:

export MALLOC_TRACE=.log
gcc -g test.c -o test
./test
mtrace test .log

结果为:

Memory not freed:
-----------------
   Address     Size     Caller
0x08ba3378    0x190  at /home/liclin/test2.c:8
0x08ba3510    0x190  at /home/liclin/test2.c:8

缺憾

mtrace本身就是通过malloc的钩子函数实现的,因此面临相同的问题——在大规模复杂系统面前无计可施。

Valgrind

 

更多

以上内存泄露检测的方法仅适用于GNU Linux环境下用户空间的程序,如果是内核代码,事情会变得复杂一些,但思路是相同的。

相关文章:

  • python之旅九【第九篇】socket
  • 【C#|.NET】利用FastDFS打造分布式文件系统
  • [LeetCode] Binary Tree Preorder Traversal 二叉树的先序遍历
  • 实用算法实现-第 24 篇 高精度整数运算
  • PHP Mysql-插入多条数据
  • Windows窗体
  • DataWorks新手引导(持续更新)
  • TOP语句放到表值函数外,效率异常低下
  • 产品经理教你玩转阿里云负载均衡SLB系列(一):快速入门--什么是负载均衡
  • Enum一点使用总结
  • 路由器相关参数及设置
  • 祝网友们龙年快乐!
  • CSS以图换字的9种方法
  • 部署Oracle 11.2.0.3 RAC (二)
  • [WinForm]DataGridView通过代码新增行问题
  • 【399天】跃迁之路——程序员高效学习方法论探索系列(实验阶段156-2018.03.11)...
  • android百种动画侧滑库、步骤视图、TextView效果、社交、搜房、K线图等源码
  • axios请求、和返回数据拦截,统一请求报错提示_012
  • bearychat的java client
  • CAP理论的例子讲解
  • CentOS学习笔记 - 12. Nginx搭建Centos7.5远程repo
  • gulp 教程
  • Java新版本的开发已正式进入轨道,版本号18.3
  • Js基础知识(一) - 变量
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • PermissionScope Swift4 兼容问题
  • Promise初体验
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 关于extract.autodesk.io的一些说明
  • 关于Flux,Vuex,Redux的思考
  • 互联网大裁员:Java程序员失工作,焉知不能进ali?
  • 前端工程化(Gulp、Webpack)-webpack
  • 前端技术周刊 2019-02-11 Serverless
  • 区块链共识机制优缺点对比都是什么
  • 如何将自己的网站分享到QQ空间,微信,微博等等
  • 删除表内多余的重复数据
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • PostgreSQL 快速给指定表每个字段创建索引 - 1
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​​​​​​​Installing ROS on the Raspberry Pi
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • #[Composer学习笔记]Part1:安装composer并通过composer创建一个项目
  • (Java岗)秋招打卡!一本学历拿下美团、阿里、快手、米哈游offer
  • (板子)A* astar算法,AcWing第k短路+八数码 带注释
  • (附源码)计算机毕业设计ssm基于B_S的汽车售后服务管理系统
  • (附源码)计算机毕业设计ssm基于Internet快递柜管理系统
  • (九)One-Wire总线-DS18B20
  • (转) ns2/nam与nam实现相关的文件
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .NET 4.0中使用内存映射文件实现进程通讯
  • .NET 反射的使用
  • .net的socket示例
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .NET设计模式(7):创建型模式专题总结(Creational Pattern)
  • /var/spool/postfix/maildrop 下有大量文件