2021-04-09

1. 核心思路

动态内存是计算机程序必不可少的组件. 在程序运行当中, 对于编译之初未知的数据流大小, 有时需要保存下来,这时就需要动态地请求内存空间. 一般地, 动态内存空间取自于堆(Heap). 所以堆是堆, 栈(Stack)是栈 两者不要放在一起, 容易混淆.

1) malloc动态分配

malloc是一个标准库函数, 我猜它的名字全称应该是(Memory allocate function). 类似功能的函数还有 calloc realloc valloc等.

简单地可以作一下理解:

calloc = malloc + memset(0)
realloc = free + malloc +memset(0) + memcpy

假如把内存当作钱, 那么malloc就是借钱, free就是还钱. 有借有还, 再借不难. 那么借钱不还呢? 或者说忘记还了呢? 这就是内存泄漏. 当项目有了一定规模之后, 想要找出内存泄漏的点, 是非常困难的. 那是否可以改造一下malloc 在借钱的时候记一下帐呢?




2. 用于DEBUG的malloc函数


1) 接口设计

接口设计无非就是要考虑需求, 需要哪些输入项, 再者就是可移植性, 是否能够完美嵌入到之前的程序当中去.

void *
     malloc(size_t size);

DESCRIPTION
The malloc(), calloc(), valloc(), realloc(), and reallocf() functions allocate memory. The allocated memory is aligned such that it
can be used for any data type, including AltiVec- and SSE-related types. The aligned_alloc() function allocates memory with the
requested alignment. The free() function frees allocations that were created via the preceding allocation functions.

首先, 这个用于DEBUG 记录分配情况的新的malloc函数肯定在原有的传参模型上进行改进, 因此必须要传参分配的字节数, 再者那就是收集的数据, 例如调用的源文件名,行号. 在最后添加一个malloc形式的函数指针, 旨在不同项目可能会有自己的分配内存函数

void* __RH_Debug_malloc( size_t size, char* FILE, int LINE, void* (*__malloc_func)(size_t size) );

2) 分析需要收集的数据

每次分配需要记录一下信息:

  • 需要分配的内存大小, 字节为单位size
  • 调用时所在的源代码文件名FILE
  • 调用时所在代码的行号LINE
  • 实际malloc的函数__malloc_func

其实每次分配出来的指针 都应该包含以上信息, 所以, 何尝不打包作成一个struct呢?

struct __RH_DebugMemoryInfo_t{
    size_t      byte;
    const char* FILE;
    uint32_t    LINE;
    void*       ptr;
};

因此, 这其实是一种映射关系, 分配的指针ptr 作为键, 其包含的结构体信息(如上)作为键值, 建立映射.

在这里我使用的是哈希映射.
一下只给出 API 作为本次构造的辅助工具 源码见Github

struct __HashList_t{
    const size_t                     key;
    const void*                const object;
    const struct __HashList_t* const pNext ;
};
typedef struct __HashList_t __HashList_t;

struct __HashMap_t{
    const __HashList_t*     const pList;
};
typedef struct __HashMap_t __HashMap_t;

__HashMap_t*  __Hash_createMap ( void );
void*         __Hash_find       ( const __HashMap_t *pHead, size_t key );
void          __Hash_pair      ( const __HashMap_t *pHead, size_t key , void* object );
void*         __Hash_get       ( const __HashMap_t *pHead, size_t key );
void*         __Hash_remove    ( const __HashMap_t *pHead, size_t key );
void          __Hash_removeAll (       __HashMap_t *pHead );

利用映射, 通过用给定指针查找到对应的内存空间,当时调用的文件及行号.因此,这个改造的debug_malloc需要记录这些信息.


3) 代码

size_t RH_Debug_alloced_byte = 0; /*分配出去的内存总字节数*/
size_t RH_Debug_free_byte    = 0; /*剩余空闲内存的总字节数*/ /*我项目不需要用到, 可自行添加逻辑*/
static __HashMap_t* pHEAD_HASHMAP_size_2_ptr = NULL;
void* __RH_Debug_malloc( size_t size, char* FILE, int LINE, void* (*__malloc_func)(size_t size) ){
    if( !pHEAD_HASHMAP_size_2_ptr )
        pHEAD_HASHMAP_size_2_ptr = __Hash_createMap();
        
    struct __RH_DebugMemoryInfo_t* pInfo = malloc(sizeof(struct __RH_DebugMemoryInfo_t));
    
    void* ptr = (*__malloc_func)(size);

    pInfo->ptr  = ptr;
    pInfo->FILE = FILE;   /* 调用时的文件名 */
    pInfo->LINE = LINE;   /* 调用时的行号  */
    pInfo->byte = size;   /* 请求字节数    */
    
    RH_Debug_alloced_byte += pInfo->byte;
    __Hash_pair(pHEAD_HASHMAP_size_2_ptr, (size_t)ptr, pInfo);
    
    return ptr;
}

void __RH_Debug_free(void* ptr, void (*__free_func)(void*)){
    struct __RH_DebugMemoryInfo_t* pInfo = (struct __RH_DebugMemoryInfo_t*)__Hash_get(pHEAD_HASHMAP_size_2_ptr, (size_t)ptr);
    RH_Debug_alloced_byte -= pInfo->byte;
         
    (*__free_func)(ptr);

}

RH_Debug_alloced_byte    分配出去的内存总字节数
pHEAD_HASHMAP_size_2_ptr    映射表头
pInfo          指针信息包

另外, 编写一个print_info每次调用时可以输出这些信息:

void* __RH_Debug_print_memory_info(void* ptr, int (*__print_func)(const char * restrict format, ...)){
    
    /* 通过映射查找信息 */
    struct __RH_DebugMemoryInfo_t* pInfo = (struct __RH_DebugMemoryInfo_t*)__Hash_get(pHEAD_HASHMAP_size_2_ptr, (size_t)ptr);

    /* 这一行是为了确保信息能够全部录入, 有时文件路径可能很长, 如果嫌麻烦 可以改成 size_t len = (常数) */
    size_t len = strlen("$DEBUG_MEM_INFO: [] [Ln ] [: byte]\n")+strlen(pInfo->FILE)+((sizeof(pInfo->LINE)+sizeof(pInfo->byte))<<3);
    
    /* alloca函数 懂的自然懂, 就是栈空间的动态分配, 退出函数自动free */
    char*  str = alloca( len + sizeof('\0') );
    
    /* 总之就是构造一句句子, C语言造字符串也是出了名地复杂 */
    snprintf(str, len, "$DEBUG_MEM_INFO: [%s] [Ln %d] [%zu:%zu Byte]\n",pInfo->FILE,pInfo->LINE,pInfo->byte,RH_Debug_alloced_byte);
    
    /* 使用你自己的打印函数, 这里没有做断言, 假定用户不调皮, 不传个NULL进来 = =! */
    (*__print_func)("%s",str);
    
    return ptr;
}



4) 锦上添花

上述代码已经能够完整的地记录每次动态分配时的信息, 但是可移植性不强, 一般开发者更习惯于malloc(x)方式去调用, 而不是__Debug_malloc(x,__FILE__,__LINE__,malloc) 所以, 如何做到完美嵌套呢?

很简单, 用.

#define RH_MALLOC(x) __RH_Debug_print_memory_info( __RH_Debug_malloc(x, __FILE__, __LINE__, malloc), printf )

在这个宏里面printf malloc是函数指针, 可以替换自己想要的等价功能接口的函数.

这样每次调用RH_MALLOC自动打印动态内存信息, 方便寻找泄漏之处.


5) 结果

在这里插入图片描述

附上在编写自研的Glucoo项目中, 使用此方法成功找到了内存泄漏之处.


3. 参考资料

[1] MALLOC(3) BSD Library Functions Manual MALLOC(3)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值