学习C/C++内存泄漏检测知识,宏定义覆盖函数检测内存泄漏

检测维修 0 161
<[id_1290304065]id_2141196762]>[id_1091053698]

近期深入研究了 C 语言和 C++ 的内存泄漏识别方法,并将其撰写成文章进行分享。

内存泄漏的两个问题

检测内存泄漏主要从上面两个问题入手。

使用宏定义覆盖 malloc 和 free 函数

在单个文件中,可以使用这种方法检测是否有内存泄漏。

#include 
#include 
int main() {
   
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    free(p1);
}

这段程序存在内存管理不当的情况,在直接执行时难以察觉。我们可以借助宏指令,替换掉系统自带的内存分配和释放函数。

我们创建了两个函数,分别为 my_malloc 和 my_free ,通过宏定义来替代原本的 malloc 和 free 功能。

#include 
#include 
void *my_malloc(size_t size, const char *file, int line) {
   
    void *p = malloc(size);
    printf(内存分配操作记录,地址信息为 %p,所需空间大小为 %ld,发生文件为 %s,具体行号为 %d 。, p, size, file, line);
    return p;
}
void my_free(void *p, const char *file, int line) {
   
    free(p);
    printf("free[-]: addr: %p\n", p);
}
#define malloc(size)    my_malloc(size, __FILE__, __LINE__)
#define free(p)         my_free(p, __FILE__, __LINE__)
int main() {
   
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    free(p1);
}

 

执行情况展示在图像中,能够识别出具体哪段指令执行了内存分配操作,并且可以确认相应的内存空间是否得到了释放。

还可以把上面的代码改进一下,把信息写入到文件中,方面查看。

#include 
#include 
#include 
#if 1
void *my_malloc(size_t size, const char *file, int line) {
   
    void *p = malloc(size);
    
    char buff[128] = {
    0 };
    sprintf(buff, "./mem/%p.mem", p); // 需要事前创建 mem 文件夹
    FILE *fp = fopen(buff, "w");
    fprintf(fp, 内存分配成功, 在位置+%s:%d, 获取到的地址是%p, 分配的字节大小为%ld, file, line, p, size);
    fflush(fp);
    fclose(fp);
    return p;
}
void my_free(void *p, const char *file, int line) {
   
    char buff[128] = {
    0 };
    sprintf(buff, "./mem/%p.mem", p); // 需要事前创建 mem 文件夹
    if (unlink(buff) < 0) {
   
        printf("double free addr:%p\n", p);
        return;
    }
    free(p);
}
#define malloc(size)    my_malloc(size, __FILE__, __LINE__)
#define free(p)         my_free(p, __FILE__, __LINE__)
#endif
int main() {
   
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    free(p1);
}

 

执行完成后,将生成一个文档,存放于 mem 目录之中,打开该文件后,可以查到未被释放的内存详情。

注意:

使用 hook 钩子

dlsym 函数可以自己实现系统调用

#include 
#include 
#include 
#if 1
// 定义回调函数类型
typedef void *(*malloc_t)(size_t size);
// 定义回调函数变量
malloc_t malloc_f = NULL;
// 同理
typedef void (*free_t)(void *p);
free_t free_f = NULL;
void *malloc(size_t size) {
   
    printf("malloc [+%s:%d]\n", __FILE__, __LINE__);
}
void free(void *p) {
   
    printf("malloc [+%s:%d]\n", __FILE__, __LINE__);
}
void init_hook(void) {
   
    
    if (malloc_f == NULL)
        malloc_f = dlsym(RTLD_NEXT, "malloc"); // dlsym 需要加上编译条件 -ldl
    if (free_f == NULL)
        free_f = dlsym(RTLD_NEXT, "free");
}
#define DEBUG_MEM_LEAK init_hook();
#endif
int main() {
   
    DEBUG_MEM_LEAK
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    free(p1);
}

 

这段程序代码,当它运用到自行构建的内存分配与释放机制时,调用打印语句会引发问题。

这是因为 printf 函数内部也调用了 malloc 来分配缓冲区空间,由此导致函数自我调用。

我们需要定义一个 flag 来防止递归的出现。

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#if 1
// 定义回调函数类型
typedef void *(*malloc_t)(size_t size);
// 定义回调函数变量
malloc_t malloc_f = NULL;
// 同理
typedef void (*free_t)(void *p);
free_t free_f = NULL;
// flag 防止递归
int enable_malloc_hook = 1;
int enable_free_hook = 1;
void *malloc(size_t size) {
   
    if (enable_malloc_hook == 1) {
   
        enable_malloc_hook = 0;
        printf("malloc [+%s:%d]\n", __FILE__, __LINE__);
        enable_malloc_hook = 1;
    }
}
void free(void *p) {
   
    if (enable_malloc_hook == 1) {
   
        enable_malloc_hook = 0;
        printf("free [+%s:%d]\n", __FILE__, __LINE__);
        enable_malloc_hook = 1;
    }
}
void init_hook(void) {
   
    

使用宏定义覆盖malloc和free_c#内存泄漏检测工具_C/C++内存泄漏检测

if (malloc_f == NULL) malloc_f = dlsym(RTLD_NEXT, "malloc"); // dlsym 需要加上编译条件 -ldl if (free_f == NULL) free_f = dlsym(RTLD_NEXT, "free"); } #define DEBUG_MEM_LEAK init_hook(); #endif int main() { DEBUG_MEM_LEAK void *p1 = malloc(10); void *p2 = malloc(20); free(p1); }

这个优化后的程序在执行时又遇到了新状况,申请内存的操作究竟是在哪一行被触发的,故障集中出现在某个特定行,这种情况肯定不正常。

我们可以借助 __builtin_return_address() 这个函数来处理, 传递 0 就能获取到前一个函数的详情, 传递 1 就能获取到再前一个函数的详情, 以此类推。

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#if 1
// 定义回调函数类型
typedef void *(*malloc_t)(size_t size);
// 定义回调函数变量
malloc_t malloc_f = NULL;
// 同理
typedef void (*free_t)(void *p);
free_t free_f = NULL;
// flag 防止递归
int enable_malloc_hook = 1;
int enable_free_hook = 1;
void *malloc(size_t size) {
   
    if (enable_malloc_hook == 1) {
   
        enable_malloc_hook = 0;
        void *caller = __builtin_return_address(0); // 返回上一层调用函数的信息
        printf("malloc [+]: %p\n", caller);
        enable_malloc_hook = 1;
    }
}
void free(void *p) {
   
    if (enable_malloc_hook == 1) {
   
        enable_malloc_hook = 0;
        void *caller = __builtin_return_address(0); // 返回上一层调用函数的信息
        printf("free [-]: %p\n", caller);
        enable_malloc_hook = 1;
    }
}
void init_hook(void) {
   
    
    if (malloc_f == NULL)
        malloc_f = dlsym(RTLD_NEXT, "malloc"); // dlsym 需要加上编译条件 -ldl
    if (free_f == NULL)
        free_f = dlsym(RTLD_NEXT, "free");
}
#define DEBUG_MEM_LEAK init_hook();
#endif
int main() {
   
    DEBUG_MEM_LEAK
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    free(p1);
}

 

执行完成后呈现图像,其中展示若干位置编号,借助 addr2line 工具能够解析出详细信息。

下面改成文件版本

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#if 1
// 定义回调函数类型
typedef void *(*malloc_t)(size_t size);
// 定义回调函数变量
malloc_t malloc_f = NULL;
// 同理
typedef void (*free_t)(void *p);
free_t free_f = NULL;
// flag 防止递归
int enable_malloc_hook = 1;
int enable_free_hook = 1;
void *malloc(size_t size) {
   
    if (enable_malloc_hook == 1) {
   
        enable_malloc_hook = 0;
        void *p = malloc_f(size);
        void *caller = __builtin_return_address(0); // 返回上一层调用函数的信息
        char buff[128] = {
    0 };
        sprintf(buff, "./mem/%p.mem", p); // 需要事前创建 mem 文件夹
        FILE *fp = fopen(buff, "w");
        fprintf(fp, 动态内存分配, 返回地址为 %p, 分配大小为 %ld, 结束输出, caller, p, size);
        fflush(fp);
        fclose(fp);
        enable_malloc_hook = 1;
        return p;
    } else {
   
        return malloc_f(size);
    }
}
void free(void *p) {
   
    if (enable_malloc_hook == 1) {
   
        enable_malloc_hook = 0;
        指针变量 caller 被赋值为宏 __builtin_return_address 的参数 0 的返回地址,该操作获取了上一层调用函数的相关信息
        
        char buff[128] = {
    0 };
        sprintf(buff, "./mem/%p.mem", p); // 需要事前创建 mem 文件夹
        if (unlink(buff) < 0) {
   
            printf("double free addr:%p\n", p);
            return;
        }
        free_f(p);
        enable_malloc_hook = 1;
    } else {
   
        free_f(p);
    }
}
void init_hook(void) {
   
    
    if (malloc_f == NULL)
        malloc_f = dlsym(RTLD_NEXT, "malloc"); // dlsym 需要加上编译条件 -ldl
    if (free_f == NULL)
        free_f = dlsym(RTLD_NEXT, "free");
}
#define DEBUG_MEM_LEAK init_hook();
#endif
int main() {
   
    DEBUG_MEM_LEAK
    void *p1 = malloc(10);
    void *p2 = malloc(20);
    free(p1);
}

 

参考连接:

那个网址是一篇在简书的文章链接,内容是关于某个主题的分享,文章标题是那个标题,文章发布在简书平台上,网址就是这篇文章的完整地址

这个链接指向一个知乎专栏文章,具体内容是关于某个主题的分享,发布者通过文字和图片相结合的方式,详细介绍了相关知识要点,并且还提供了相关的参考资料供读者进一步学习,整体内容比较丰富实用。

这个链接是一个知乎专栏文章的地址,它分享了一些关于个人成长和职场发展的心得体会,内容比较实用,值得一看。

内容来自网络,具体出处为博客园,作者名为weixin_61021362,文章详细介绍了相关技术,详情请访问链接,网址为https://blog.csdn.net/weixin_61021362/article/details/128861266。

相关推荐: