zoukankan      html  css  js  c++  java
  • 怎样检测内存泄露

    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环境下用户空间的程序,如果是内核代码,事情会变得复杂一些,但思路是相同的。

  • 相关阅读:
    安装Eclipse for MAC 苹果版
    visual studio code emmet 插件不提示?解决方案
    支付宝付款页面调整屏幕亮度
    浅谈iOS需要掌握的技术点
    Objective-C 编程艺术 (Zen and the Art of the Objective-C Craftsmanship 中文翻译)
    ios开发数据库版本迁移手动更新迭代和自动更新迭代艺术(-)
    ios 添加到cell 上的button点击无效!扩大button的点击区域(黑魔法)
    个人中心模块-拍照剪裁上传
    利用js与java交互
    显示gif动画(帧动画的播放)
  • 原文地址:https://www.cnblogs.com/balaamwe/p/2314664.html
Copyright © 2011-2022 走看看