Skip to Content
Course content

250: Practice Exercise: Building a Simple Memory Leak Detector

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've noticed a recurring theme when I review code from developers moving into C: the belief that if a program doesn't crash and the OS cleans up everything after the process exits, then a "small" memory leak isn't actually a problem. You might think, "Why bother tracking a few bytes here and there if the kernel just wipes the slate clean when I hit Ctrl+C?"

The Myth: "If it doesn't crash, it's fine"

Here is why that thinking will get you fired in a production environment. Let's look at a specific scenario. Imagine you're writing a network packet processor. Every time a packet arrives, you allocate a small buffer to hold the header. If you forget to free that buffer, you might only be leaking 64 bytes per packet. That seems trivial, right?

while (server_running) {
    char *header = malloc(64); 
    process_packet(header);
    // Oops, forgot free(header);
}

On your laptop, during a five-minute test, you'll never notice. But in a production environment processing 10,000 packets per second, you're leaking roughly 640KB every second. In an hour, you've eaten 2.3GB of RAM. Eventually, the system hits the OOM (Out of Memory) killer, and your process is nuked. The tragedy is that the crash happens hours after the actual bug occurred, making it a nightmare to debug.

The Reality: Tracking the Lifecycle of Every Byte

Since C doesn't have a garbage collector, we have to be our own accountants. To build a leak detector, you can't just look at the pointers you currently have; you have to keep a record of every allocation you've ever made that hasn't been matched with a corresponding free.

The most effective way to do this without rewriting your entire codebase is to "wrap" the standard library functions. I usually create a custom my_malloc and my_free. Instead of just calling the original function, my wrapper adds the address and the size of the allocation to a global linked list. When my_free is called, I find that address in my list and remove it.

typedef struct Allocation {
    void *address;
    size_t size;
    struct Allocation *next;
} Allocation;

Allocation *head = NULL;

void *my_malloc(size_t size) {
    void *ptr = malloc(size);
    if (ptr) {
        Allocation *node = malloc(sizeof(Allocation));
        node->address = ptr;
        node->size = size;
        node->next = head;
        head = node;
    }
    return ptr;
}

Now, I have a ledger. If I call a report_leaks() function at the very end of main() and the head pointer isn't NULL, I know exactly how many bytes I leaked and where they are. (Note: In a real-world tool, you'd use a hash map for performance, but a linked list is the best way to understand the concept.)

Accounting for the Registry Itself

There's a catch here that often trips people up: my my_malloc function calls malloc to create the Allocation node itself. If I'm not careful, my leak detector will report its own bookkeeping nodes as leaks!

I handle this by using a separate flag or by calling the raw malloc for the internal nodes instead of the wrapped version. It's a bit of a "meta" problem, but it's the kind of detail that separates a tool that works from a tool that just creates noise.




📋 Practical Task

Implementation: The Allocation Ledger System

Your goal is to complete a simple memory leak detector. I've provided the skeleton of the wrapper functions, but the logic for tracking and reporting is missing. You need to implement the registry logic so the program can tell us exactly how much memory was forgotten.

Requirements:

  • Implement my_malloc: It should allocate the requested memory and then create an Allocation node to store the address and size in a global linked list.
  • Implement my_free: It should find the corresponding Allocation node for the pointer being freed, remove that node from the list, and then call the real free() on both the node and the original pointer.
  • Implement report_leaks: This function should traverse the linked list. If the list is empty, print "No leaks detected!". If not, print the total number of bytes leaked and the number of allocations that weren't freed.
#include <stdio.h>
#include <stdlib.h>

typedef struct Allocation {
    void *address;
    size_t size;
    struct Allocation *next;
} Allocation;

Allocation *registry = NULL;

void *my_malloc(size_t size) {
    // TODO: Implement allocation and registry tracking
}

void my_free(void *ptr) {
    // TODO: Implement registry removal and freeing
}

void report_leaks() {
    // TODO: Implement the leak reporting logic
}

int main() {
    void *a = my_malloc(100);
    void *b = my_malloc(200);
    my_free(a);
    
    // We intentionally don't free 'b' to test the detector
    report_leaks(); 
    
    return 0;
}
Rating
0 0

There are no comments for now.

to be the first to leave a comment.