近期深入研究了 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) {

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。