内存泄漏是C/C++服务器开发中常见的问题之一,特别是在长时间运行的服务器程序中。下面我将介绍如何手写一个内存泄漏检测组件。
内存泄漏检测的基本原理是: 1. 重载malloc/free等内存分配函数 2. 记录每次内存分配和释放的信息 3. 在程序结束时检查未释放的内存块
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
typedef struct _mem_info {
void* ptr; // 分配的内存指针
size_t size; // 分配的大小
const char* file; // 分配所在的文件
int line; // 分配所在的行号
struct _mem_info* next; // 下一个节点
} mem_info;
static mem_info* mem_list = NULL;
static pthread_mutex_t mem_mutex = PTHREAD_MUTEX_INITIALIZER;
void* my_malloc(size_t size, const char* file, int line) {
void* ptr = malloc(size);
if (ptr != NULL) {
mem_info* info = (mem_info*)malloc(sizeof(mem_info));
info->ptr = ptr;
info->size = size;
info->file = file;
info->line = line;
pthread_mutex_lock(&mem_mutex);
info->next = mem_list;
mem_list = info;
pthread_mutex_unlock(&mem_mutex);
}
return ptr;
}
void my_free(void* ptr) {
if (ptr == NULL) return;
pthread_mutex_lock(&mem_mutex);
mem_info** p = &mem_list;
while (*p != NULL) {
if ((*p)->ptr == ptr) {
mem_info* tmp = *p;
*p = (*p)->next;
free(tmp);
free(ptr);
pthread_mutex_unlock(&mem_mutex);
return;
}
p = &((*p)->next);
}
pthread_mutex_unlock(&mem_mutex);
// 未找到对应的分配记录
fprintf(stderr, "Invalid free: %p\n", ptr);
free(ptr); // 仍然释放,避免崩溃
}
#define malloc(size) my_malloc(size, __FILE__, __LINE__)
#define free(ptr) my_free(ptr)
void check_leaks() {
pthread_mutex_lock(&mem_mutex);
mem_info* p = mem_list;
if (p == NULL) {
printf("No memory leaks detected.\n");
} else {
printf("Memory leaks detected:\n");
size_t total_leak = 0;
int leak_count = 0;
while (p != NULL) {
printf("Leak %d: %zu bytes at %p, allocated at %s:%d\n",
++leak_count, p->size, p->ptr, p->file, p->line);
total_leak += p->size;
mem_info* tmp = p;
p = p->next;
free(tmp->ptr); // 释放泄漏的内存
free(tmp); // 释放记录节点
}
printf("Total %d leaks, %zu bytes\n", leak_count, total_leak);
mem_list = NULL;
}
pthread_mutex_unlock(&mem_mutex);
}
#include "leak_detector.h"
int main() {
// 在程序退出前注册泄漏检测
atexit(check_leaks);
// 测试代码
int* p1 = (int*)malloc(sizeof(int)*10);
char* p2 = (char*)malloc(100);
free(p1); // 故意不释放p2
return 0;
}
void* my_calloc(size_t nmemb, size_t size, const char* file, int line) {
void* ptr = calloc(nmemb, size);
if (ptr != NULL) {
mem_info* info = (mem_info*)malloc(sizeof(mem_info));
info->ptr = ptr;
info->size = nmemb * size;
info->file = file;
info->line = line;
pthread_mutex_lock(&mem_mutex);
info->next = mem_list;
mem_list = info;
pthread_mutex_unlock(&mem_mutex);
}
return ptr;
}
void* my_realloc(void* ptr, size_t size, const char* file, int line) {
if (ptr == NULL) {
return my_malloc(size, file, line);
}
pthread_mutex_lock(&mem_mutex);
mem_info** p = &mem_list;
while (*p != NULL) {
if ((*p)->ptr == ptr) {
void* new_ptr = realloc(ptr, size);
if (new_ptr != NULL) {
(*p)->ptr = new_ptr;
(*p)->size = size;
(*p)->file = file;
(*p)->line = line;
}
pthread_mutex_unlock(&mem_mutex);
return new_ptr;
}
p = &((*p)->next);
}
pthread_mutex_unlock(&mem_mutex);
// 未找到原始分配记录
fprintf(stderr, "Invalid realloc: %p\n", ptr);
return realloc(ptr, size);
}
#define calloc(nmemb, size) my_calloc(nmemb, size, __FILE__, __LINE__)
#define realloc(ptr, size) my_realloc(ptr, size, __FILE__, __LINE__)
使用线程局部存储来支持多线程环境:
static __thread mem_info* thread_mem_list = NULL;
// 修改my_malloc/my_free等函数使用thread_mem_list代替mem_list
// 在check_leaks中需要遍历所有线程的内存列表
使用哈希表代替链表可以提高查找效率:
#define HASH_SIZE 1024
typedef struct _mem_info {
void* ptr;
size_t size;
const char* file;
int line;
struct _mem_info* next;
} mem_info;
static mem_info* mem_hash[HASH_SIZE];
static pthread_mutex_t mem_mutex[HASH_SIZE];
static inline unsigned int hash_ptr(void* ptr) {
return ((unsigned long)ptr >> 4) % HASH_SIZE;
}
// 修改my_malloc/my_free使用哈希表存储
如果不想自己实现,也可以考虑以下成熟工具: - Valgrind - AddressSanitizer (ASan) - mtrace (glibc自带工具)
这个手写的内存泄漏检测组件虽然简单,但包含了基本原理,可以根据项目需求进行扩展和优化。