Простая программа для обнаружения утечек памяти в нашей программе на C

Всякий раз, когда мы программируем на таком языке, как 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)
Вход в полноэкранный режим Выход из полноэкранного режима

Из приведенного выше вывода видно, что программа обнаруживает все утечки памяти, причем дважды.

Примечание: Эта программа не сможет отследить выделение памяти, сделанное встроенной или сторонней библиотекой. Поэтому, если мы освободим память, выделенную в вызове библиотеки, она покажет двойное освобождение, мы можем просто проигнорировать их.


❤️ Большое спасибо, что прочитали эту статью. Я страстный студент, изучающий новые вещи, поэтому если вы найдете какие-либо ошибки или у вас есть какие-либо предложения, пожалуйста, дайте мне знать в комментариях.

Кроме того, поделитесь со мной и поставьте большой палец вверх, если эта статья вам чем-то помогла.

Оцените статью
devanswers.ru
Добавить комментарий