Всякий раз, когда мы программируем на таком языке, как C/C++, нам приходится самим управлять своей памятью, нет возможности автоматически освобождать неиспользуемую память, как это происходит в других языках, таких как Python, Java и др.
Иногда мы пишем программы, в которых оставляем утечки памяти, что приводит к краху программы через некоторое время, и очень трудно найти место утечки памяти.
Поэтому для отладки такой проблемы мы можем написать простую программу для обнаружения утечек памяти в нашей программе.
Идея заключается в том, чтобы отслеживать каждое выделение памяти и проверять, была ли она освобождена во время выполнения программы.
Начнем с базовой структуры данных для хранения информации о распределении памяти во время выполнения программы.
Код структуры данных
// We assume max 1000 allocations will take place
#define MAX_ALLOCATIONS 1000
/*
Data Structure to keep track of memory allocations
address -> Memory address allocated
size -> Size allocated
line -> Line number where allocation is done
*/
typedef struct {
size_t address;
size_t size;
uint32_t line;
} Mem;
/*
mem -> store all the allocation in array
total_allocated_size -> Keep track total memory allocated
total_free_size -> Keep track total memory freed
*/
struct {
Mem mem[MAX_ALLOCATIONS];
size_t total_allocated_size;
size_t total_free_size;
} data;
Теперь мы создадим несколько вспомогательных функций для поиска, вставки и стирания выделения памяти в структуре данных.
Код вспомогательных функций
/**
* Find a memory by its address
*
* @return: Pointer to memory
*/
Mem *find_by_address(size_t address) {
for (uint32_t i=0; i<MAX_ALLOCATIONS; i++) {
if (data.mem[i].address == address)
return &data.mem[i]; // as soon as find return
}
// otherwise return null
return NULL;
}
/**
* insert memory allocated with size
*/
void insert(size_t address, size_t size, uint32_t line) {
// check for null
if (address == 0) {
WARN("Memory allocation failed", line);
return;
}
Mem *mem = find_by_address(0);
// if the return value is null we need to increase the MAX_ALLOCATIONS value
if (mem == NULL) {
WARN("Max allocations reached", line);
return;
}
// save all the allocation info
mem->address = address;
mem->size = size;
mem->line = line;
data.total_allocated_size += size;
}
/**
* Remove the memory allocation
*
* @return: -1 on failure else 0
*/
int erase(size_t address, uint32_t line) {
if (address == 0) {
WARN("Tried to free a null ptr", line);
return -1;
}
Mem *mem = find_by_address(address);
// if the address is not found we assume it is already deleted
if (mem == NULL) {
WARN("Double free detected", line);
return -1;
}
// set address to null and update info
mem->address = 0;
data.total_free_size += mem->size;
return 0;
}
В конце программы мы выведем подробный отчет об утечках памяти.
Код
void print_report() {
printf("nLeak Summaryn");
printf("Total Memory allocated %lu bytesn", data.total_allocated_size);
printf("Total Memory freed %lu bytesn", data.total_free_size);
printf("Memory Leaked %lu bytesnn",
data.total_allocated_size - data.total_free_size);
if (data.total_free_size != data.total_allocated_size) {
printf("Detailed Reportn");
for (int i=0; i<MAX_ALLOCATIONS; i++) {
if (data.mem[i].address != 0) {
printf("Memory leak at line %d: (%lu bytes)n",
data.mem[i].line,
data.mem[i].size);
}
}
}
}
Теперь создадим перехватываемые функции аллокатора по умолчанию для вставки при выделении памяти и стирания при освобождении.
Пользовательские функции аллокатора
// Create allocator functions
void *_malloc(size_t size, uint32_t line) {
void *ptr = malloc(size);
// insert to memory data
insert((size_t)ptr, size, line);
return ptr;
}
void _free(void *ptr, uint32_t line) {
// erase memory data
if (erase((size_t)ptr, line) == 0)
free(ptr);
}
Здесь мы переопределим стандартные функции аллокатора, такие как malloc
и free
с помощью наших перехватываемых функций.
Переопределение функций аллокатора по умолчанию
// redefine allocator functions
#define malloc(size) _malloc(size, __LINE__)
#define free(ptr) _free(ptr, __LINE__)
Вот и все!
Полный код
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#define MAX_ALLOCATIONS 100
#define WARN(msg, line) (printf("Warning %d: %sn", line, msg))
/*
Data Structure to keep track of memory allocations
*/
typedef struct {
size_t address;
size_t size;
uint32_t line;
} Mem;
struct {
Mem mem[MAX_ALLOCATIONS];
size_t total_allocated_size;
size_t total_free_size;
} data;
/**
* Find a memory by its address
*
* @return: Pointer to memory
*/
Mem *find_by_address(size_t address) {
for (uint32_t i=0; i<MAX_ALLOCATIONS; i++) {
if (data.mem[i].address == address)
return &data.mem[i]; // as soon as find return
}
// otherwise return null
return NULL;
}
/**
* insert memory allocated with size
*/
void insert(size_t address, size_t size, uint32_t line) {
// check for null
if (address == 0) {
WARN("Memory allocation failed", line);
return;
}
Mem *mem = find_by_address(0);
// if the return value is null we need to increase the MAX_ALLOCATIONS value
if (mem == NULL) {
WARN("Max allocations reached", line);
return;
}
// save all the allocation info
mem->address = address;
mem->size = size;
mem->line = line;
data.total_allocated_size += size;
}
/**
* Remove the memory allocation
*
* @return: -1 on failure else 0
*/
int erase(size_t address, uint32_t line) {
if (address == 0) {
WARN("Tried to free a null ptr", line);
return -1;
}
Mem *mem = find_by_address(address);
// if the address is not found we assume it is already deleted
if (mem == NULL) {
WARN("Double free detected", line);
return -1;
}
// set address to null and update info
mem->address = 0;
data.total_free_size += mem->size;
return 0;
}
void print_report() {
printf("nLeak Summaryn");
printf("Total Memory allocated %lu bytesn", data.total_allocated_size);
printf("Total Memory freed %lu bytesn", data.total_free_size);
printf("Memory Leaked %lu bytesnn",
data.total_allocated_size - data.total_free_size);
if (data.total_free_size != data.total_allocated_size) {
printf("Detailed Reportn");
for (int i=0; i<MAX_ALLOCATIONS; i++) {
if (data.mem[i].address != 0) {
printf("Memory leak at line %d: (%lu bytes)n",
data.mem[i].line,
data.mem[i].size);
}
}
}
}
// Override allocation functions
void *_malloc(size_t size, uint32_t line) {
void *ptr = malloc(size);
// insert to memory data
insert((size_t)ptr, size, line);
return ptr;
}
void _free(void *ptr, uint32_t line) {
// erase memory data
if (erase((size_t)ptr, line) == 0)
free(ptr);
}
// redefine allocator functions
#define malloc(size) _malloc(size, __LINE__)
#define free(ptr) _free(ptr, __LINE__)
int main() {
int *n1 = malloc(sizeof(int));
free(n1);
int *n2 = NULL;
free(n2);
int *n3 = malloc(sizeof(int));
free(n3);
free(n3);
int *n4 = malloc(sizeof(int));
print_report();
return 0;
}
Вывод
naman@namantam1:~/programs$ gcc main.c && ./a.out
Warning 143: Tried to free a null ptr
Warning 147: Double free detected
Leak Summary
Total Memory allocated 12 bytes
Total Memory freed 8 bytes
Memory Leaked 4 bytes
Detailed Report
Memory leak at line 149: (4 bytes)
Из приведенного выше вывода видно, что программа обнаруживает все утечки памяти, причем дважды.
Примечание: Эта программа не сможет отследить выделение памяти, сделанное встроенной или сторонней библиотекой. Поэтому, если мы освободим память, выделенную в вызове библиотеки, она покажет двойное освобождение, мы можем просто проигнорировать их.
❤️ Большое спасибо, что прочитали эту статью. Я страстный студент, изучающий новые вещи, поэтому если вы найдете какие-либо ошибки или у вас есть какие-либо предложения, пожалуйста, дайте мне знать в комментариях.
Кроме того, поделитесь со мной и поставьте большой палец вверх, если эта статья вам чем-то помогла.