Published on

memory leak detection for robots(!humans)

Memory Leak Detection Using Instrumented Allocators

Introduction

Memory leaks are a common issue in software development, especially in languages like C and C++ that use manual memory management. A memory leak occurs when allocated memory is not properly deallocated, leading to unnecessary memory consumption that can degrade performance and cause system crashes. although memory leaks can be managed easily if we are careful enough we will exploring one of the tricks that i invented for my use cases and i see people complaining about memory all the time not giving it a thought if you know better ways to manage then please let me know

Detecting and fixing memory leaks is challenging because memory allocations are scattered throughout the codebase. To make this easier, we can use an instrumented allocator, which records details about memory allocations and deallocations, helping us pinpoint leaks.


Understanding Memory Leaks

A memory leak happens when:

  • Memory is allocated using malloc(), calloc(), or new, but never freed using free() or delete.
  • A reference to allocated memory is lost before it is deallocated.
  • Objects are retained longer than necessary in automatic memory management systems (reference leaks).

Example of a Memory Leak in C

#include <stdlib.h>

void memory_leak() {
    int *ptr = malloc(sizeof(int) * 100);
    // Memory is allocated but never freed, leading to a memory leak
}

Each call to memory_leak() will leak 100 integers, gradually consuming memory until the system crashes.


Using an Instrumented Allocator

An instrumented allocator is a wrapper around malloc() and free() that records metadata (such as file name and line number) when memory is allocated.

Instrumented Memory Allocation

Instead of calling malloc() directly, we define a wrapper function:

#include <stdio.h>
#include <stdlib.h>

void* my_malloc(size_t size, const char* file, int line) {
    void* ptr = malloc(size);
    if (ptr) {
        printf("Allocated %zu bytes at %p (File: %s, Line: %d)\n", size, ptr, file, line);
    }
    return ptr;
}

void my_free(void* ptr, const char* file, int line) {
    printf("Freed memory at %p (File: %s, Line: %d)\n", ptr, file, line);
    free(ptr);
}

#define malloc(size) my_malloc(size, __FILE__, __LINE__)
#define free(ptr) my_free(ptr, __FILE__, __LINE__)

Now, when we allocate memory using malloc(), it logs the file and line number where the allocation occurred.

Usage Example

int main() {
    int *data = malloc(sizeof(int) * 50);
    free(data);
    return 0;
}

Output:

Allocated 200 bytes at 0x600003f040 (File: main.c, Line: 21)
Freed memory at 0x600003f040 (File: main.c, Line: 22)

Tracking Memory Leaks

To detect leaks, we maintain a linked list of all active allocations:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct MemInfo {
    void* ptr;
    size_t size;
    const char* file;
    int line;
    struct MemInfo* next;
} MemInfo;

MemInfo* mem_list = NULL;

void add_mem_info(void* ptr, size_t size, const char* file, int line) {
    MemInfo* new_node = (MemInfo*)malloc(sizeof(MemInfo));
    new_node->ptr = ptr;
    new_node->size = size;
    new_node->file = file;
    new_node->line = line;
    new_node->next = mem_list;
    mem_list = new_node;
}

void remove_mem_info(void* ptr) {
    MemInfo** current = &mem_list;
    while (*current) {
        if ((*current)->ptr == ptr) {
            MemInfo* temp = *current;
            *current = (*current)->next;
            free(temp);
            return;
        }
        current = &((*current)->next);
    }
}

void* my_malloc(size_t size, const char* file, int line) {
    void* ptr = malloc(size);
    if (ptr) {
        add_mem_info(ptr, size, file, line);
        printf("Allocated %zu bytes at %p (File: %s, Line: %d)\n", size, ptr, file, line);
    }
    return ptr;
}

void my_free(void* ptr, const char* file, int line) {
    remove_mem_info(ptr);
    printf("Freed memory at %p (File: %s, Line: %d)\n", ptr, file, line);
    free(ptr);
}

void check_leaks() {
    MemInfo* current = mem_list;
    if (current) {
        printf("Memory Leaks Detected:\n");
        while (current) {
            printf("Leaked %zu bytes at %p (File: %s, Line: %d)\n",
                   current->size, current->ptr, current->file, current->line);
            current = current->next;
        }
    } else {
        printf("No memory leaks detected!\n");
    }
}

#define malloc(size) my_malloc(size, __FILE__, __LINE__)
#define free(ptr) my_free(ptr, __FILE__, __LINE__)

int main() {
    int* data = malloc(sizeof(int) * 50); // Intentionally forgetting to free()
    check_leaks();
    return 0;
}

Expected Output if Memory Leaks Exist

Allocated 200 bytes at 0x600003f040 (File: main.c, Line: 57)
Memory Leaks Detected:
Leaked 200 bytes at 0x600003f040 (File: main.c, Line: 57)

Expected Output if No Leaks Exist

Allocated 200 bytes at 0x600003f040 (File: main.c, Line: 57)
Freed memory at 0x600003f040 (File: main.c, Line: 58)
No memory leaks detected!

Advantages of This Approach

  1. Automatic Leak Detection – Unfreed memory is logged with file and line info.
  2. Easy Debugging – Directly pinpoints memory leaks.
  3. Minimal Performance Overhead – Simple linked list tracking.

Conclusion

Instrumented allocators are a powerful tool for detecting and fixing memory leaks in C and C++. By logging allocations and frees, we can quickly identify leaks and prevent memory-related crashes.

For large projects, combining this approach with tools like Valgrind or AddressSanitizer can further enhance memory debugging and ensure leak-free, efficient memory management.