A simple program to detect memory leaks in our C program
Tags: clanguage, memoryleak, lowlevelprogramming, os
Published: 2022-07-25 17:47:00

Whenever we program in a language such as C/C++ we have to manage our memory ourselves, there is no way to free unused memory automatically as we get in other languages like Python, Java, etc.

Sometimes we write programs where we leave memory leaks, resulting in a crash of the program after some time and It is very difficult to find where the memory leak is left.

So to debug such an issue we can write a simple program to detect memory leaks in our program.

The idea here is to keep track of every allocation and check Whether it was freed during the program's runtime. 

Let's start with a basic data structure to store memory allocation information during the execution of the program.

**Data structure code**

```c
// 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;
```

Now we will create some helper functions to find, insert and erase memory allocation in the data structure.

**Helper functions code**

```c
/**
 * 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;
}
```

At the end of the program, we will print a detailed report of memory leaks.

**Code**

```c
void print_report() {
    printf("\nLeak Summary\n");
    printf("Total Memory allocated %lu bytes\n", data.total_allocated_size);
    printf("Total Memory freed     %lu bytes\n", data.total_free_size);
    printf("Memory Leaked          %lu bytes\n\n", 
        data.total_allocated_size - data.total_free_size);

    if (data.total_free_size != data.total_allocated_size) {
        printf("Detailed Report\n");
        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);
            }
        }
    }
}
```

Now create default allocator intercepted functions to insert while memory allocation and erase while free.

**Custom allocator functions**

```c
// 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);
}
```

Here we redefine the default allocator function such as `malloc` and `free` with our intercepted functions.

**Redefine default allocator functions**

```c
// redefine allocator functions
#define malloc(size) _malloc(size, __LINE__)
#define free(ptr) _free(ptr, __LINE__)
```

That's it!

**Complete Code**

```c
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define MAX_ALLOCATIONS 100
#define WARN(msg, line) (printf("Warning %d: %s\n", 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 Summary\n");
    printf("Total Memory allocated %lu bytes\n", data.total_allocated_size);
    printf("Total Memory freed     %lu bytes\n", data.total_free_size);
    printf("Memory Leaked          %lu bytes\n\n", 
        data.total_allocated_size - data.total_free_size);

    if (data.total_free_size != data.total_allocated_size) {
        printf("Detailed Report\n");
        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;
}
```

**Output**

```bash
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)
```

From the above output, It is clear that it detects all the memory leaks, double-free.

> **Note**: This program will not be able to keep track of memory allocation done by inbuild or any third-party library. So if we free memory allocated in library call, It will show double free, We can simply ignore them.

---

❤️Thank you so much for reading this article. I'm a passionate engineering student learning new things so If you find any mistakes or have any suggestions please let me know in the comments.

Also, consider sharing and giving a thumbs up If this post helps you in any way.