545
社区成员
发帖
与我相关
我的任务
分享内存泄漏(Memory Leak)是指程序在运行过程中动态分配的内存(通过malloc、new等操作)在使用完毕后未能正确释放,导致这部分内存无法被程序后续访问或回收,直到程序结束才由操作系统回收。简单来说,就是申请的内存忘记归还给系统。
内存泄漏的严重性在于它是一个累积性错误——即使单次泄漏的内存很小,但随着时间的推移,泄漏的内存会不断累积,最终导致可用内存耗尽,程序性能下降,甚至崩溃。
Java、Go、Python等语言都具备垃圾回收(Garbage Collection,GC)机制。GC会定期扫描堆内存,自动回收不再被引用的内存对象,程序员无需手动释放内存。虽然GC也并非万能(可能因引用未断开导致“内存堆积”),但绝大多数情况下,内存管理是自动化的。
而C/C++没有内置的GC机制,内存管理完全交由程序员手动控制:
这种设计赋予程序员极大的灵活性,但也带来了责任——任何malloc/new都必须有对应的free/delete,否则就会发生内存泄漏。此外,C/C++中指针的灵活使用(指针运算、指针别名等)也使得内存泄漏问题更加隐蔽和复杂。
常见的内存泄漏场景包括:
内存泄漏泄漏的是堆内存(Heap Memory)。
| 特性 | 栈(Stack) | 堆(Heap) |
|---|---|---|
| 管理方式 | 编译器自动管理 | 程序员手动管理 |
| 分配速度 | 快(仅移动栈指针) | 慢(需要寻找合适空闲块) |
| 存储内容 | 局部变量、函数参数、返回地址 | 动态分配的对象、数据 |
| 生命周期 | 随函数调用结束自动释放 | 需手动释放,或程序结束才回收 |
| 内存大小 | 较小(通常几MB) | 较大(受系统内存限制) |
| 碎片化 | 无(先进后出) | 易产生外部碎片 |
| 典型分配 | int a = 10; | int p = (int)malloc(sizeof(int)); |
代码归属:
线上环境不能随便停服务,也不能直接挂载Valgrind等重型工具(性能影响太大)。解决思路是让检测模块具备动态开关能力,实现热更新。
典型做法:定义一个全局标志g_mem_leak_check_enabled,在malloc/free的包装函数中判断该标志,只有开启时才进行记录操作。
维护一个全局链表或红黑树,每次malloc时插入节点(记录指针地址、大小、调用位置),每次free时删除节点。程序结束时,检查容器中是否还有残留节点,若有则说明发生了泄漏。
优点:
缺点:
每次malloc时创建一个临时文件,文件名包含指针地址,文件内容记录分配信息(文件名、函数名、行号、大小)。每次free时删除对应文件。程序结束后,查看剩余文件即可定位泄漏点。
优点:
缺点:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
// 确保block目录存在
static void ensure_block_dir() {
struct stat st = {0};
if (stat("./block", &st) == -1) {
mkdir("./block", 0755);
}
}
void *nMalloc(size_t size, const char *filename, const char *funcname, int line) {
void *ptr = malloc(size);
if (!ptr) return NULL;
ensure_block_dir();
char buff[256];
snprintf(buff, sizeof(buff), "./block/%p.mem", ptr);
FILE *fp = fopen(buff, "w");
if (!fp) {
free(ptr);
return NULL;
}
fprintf(fp, "[+][%s:%s:%d] %p %zu malloc\n", filename, funcname, line, ptr, size);
fclose(fp);
return ptr;
}
void nFree(void *ptr, const char *filename, const char *funcname, int line) {
if (!ptr) return;
char buff[256];
snprintf(buff, sizeof(buff), "./block/%p.mem", ptr);
if (unlink(buff) < 0) {
// 文件不存在,可能是重复释放或未记录的指针
printf("Warning: double free or free of unknown pointer: %p at [%s:%s:%d]\n",
ptr, filename, funcname, line);
}
free(ptr);
}
#define malloc(size) nMalloc(size, __FILE__, __func__, __LINE__)
#define free(ptr) nFree(ptr, __FILE__, __func__, __LINE__)
// 测试代码
int main() {
int size = 5;
void *p1 = malloc(size);
void *p2 = malloc(size * 2);
void *p3 = malloc(size * 3);
free(p1);
free(p3); // p2 故意不释放,用于演示泄漏
return 0;
}
通过dlsym(RTLD_NEXT, "malloc")获取真实的malloc/free函数地址,然后编写同名包装函数,在其中插入记录逻辑。这种方式无需修改业务代码,通过链接器实现全局拦截。
优点:
__builtin_return_address)缺点:
#define _GNU_SOURCE
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <link.h>
#include <sys/stat.h>
// 将地址转换为可执行文件内的相对偏移(用于符号解析)
static void *addr_to_offset(void *addr) {
Dl_info info;
struct link_map *link;
if (dladdr1(addr, &info, (void **)&link, RTLD_DL_LINKMAP)) {
return (void *)((size_t)addr - link->l_addr);
}
return addr;
}
typedef void *(*malloc_t)(size_t size);
typedef void (*free_t)(void *ptr);
malloc_t real_malloc = NULL;
free_t real_free = NULL;
// 防止递归调用的线程局部标志
static __thread bool in_hook = false;
static void ensure_block_dir() {
struct stat st = {0};
if (stat("./block", &st) == -1) {
mkdir("./block", 0755);
}
}
void *malloc(size_t size) {
if (!real_malloc) {
real_malloc = dlsym(RTLD_NEXT, "malloc");
}
// 如果已经在hook中,直接调用真实malloc,避免递归
if (in_hook) {
return real_malloc(size);
}
in_hook = true;
void *ptr = real_malloc(size);
if (ptr) {
ensure_block_dir();
void *caller = __builtin_return_address(0); // 获取调用malloc的地址
void *offset = addr_to_offset(caller);
char buff[256];
snprintf(buff, sizeof(buff), "./block/%p.mem", ptr);
FILE *fp = fopen(buff, "w");
if (fp) {
fprintf(fp, "[+][%p] %p %zu malloc\n", offset, ptr, size);
fclose(fp);
}
}
in_hook = false;
return ptr;
}
void free(void *ptr) {
if (!ptr) return;
if (!real_free) {
real_free = dlsym(RTLD_NEXT, "free");
}
if (in_hook) {
real_free(ptr);
return;
}
in_hook = true;
char buff[256];
snprintf(buff, sizeof(buff), "./block/%p.mem", ptr);
if (unlink(buff) < 0) {
// 可能重复释放或未记录
}
real_free(ptr);
in_hook = false;
}
// 测试代码
int main() {
int size = 5;
void *p1 = malloc(size);
void *p2 = malloc(size * 2);
void *p3 = malloc(size * 3);
free(p1);
free(p3); // p2 泄漏
return 0;
}
然后通过addr2line -e ./memleak -f -a可以查看是哪里内存泄漏了:

内存泄漏是C/C++程序员必须面对的挑战。通过宏定义或hook技术,我们可以实现轻量级的内存泄漏检测组件,在开发和测试阶段快速定位问题。对于线上服务,可以设计动态开关机制,在必要时开启检测,平衡性能与问题排查的需求。
https://blog.csdn.net/qq_57951250/article/details/159047819?spm=1011.2124.3001.6209