Skip to Content
Course content

58: Using Valgrind to Detect Memory Errors

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

Up until now, we've been trusting our instincts with malloc and free. In a small program, that usually works. But as soon as your projects grow, "hoping" you freed every pointer is a recipe for a midnight debugging session when your server crashes because it ran out of RAM. This is where Valgrind comes in.

Valgrind isn't a compiler; it's a tool that runs your compiled binary in a virtual environment to track every single byte of memory your program touches. Specifically, we'll be using the Memcheck tool, which is the gold standard for finding leaks and "use-after-free" bugs.

Building a Leaky String Array

Let's build a small utility that takes a number of entries and allocates a list of strings. I want to simulate a simple database record fetch. I'll write this quickly, and I'll intentionally leave a gap in my logic so we have something to find.

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

void process_records(int count) {
    // Allocate the array of pointers
    char **records = malloc(count * sizeof(char *));
    
    for (int i = 0; i < count; i++) {
        // Allocate each individual string
        records[i] = malloc(20 * sizeof(char));
        sprintf(records[i], "Record %d", i);
        printf("Processed: %s\n", records[i]);
    }

    // Cleanup phase
    for (int i = 0; i < count; i++) {
        free(records[i]);
    }
    // I'm intentionally forgetting to free(records) here.
}

int main() {
    process_records(5);
    return 0;
}

To the naked eye, this looks mostly correct. I'm freeing the individual strings inside the loop. If I run this, it finishes instantly and prints the output perfectly. The OS will reclaim the memory once the process exits, so you won't see your system RAM vanish, but in a long-running app, this is a slow-motion train wreck.

Running the Memcheck Tool

Before we run Valgrind, you must compile with the -g flag. If you don't, Valgrind can tell you that memory leaked, but it can't tell you where in the source code it happened because the debugging symbols are missing.

gcc -g -o record_test record_test.c
valgrind --leak-check=full ./record_test

I use --leak-check=full because the default settings are often too vague. I want to see exactly which allocation wasn't freed.

Interpreting the "Definitely Lost" Report

When I run the command above, I get a wall of text. Don't let it intimidate you; look for the "LEAK SUMMARY" at the bottom. I'm seeing this:

==12345== LEAK SUMMARY:
==12345==    definitely lost: 40 bytes in 1 blocks
==12345==    indirectly lost: 0 bytes in 0 blocks
==12345==      possibly lost: 0 bytes in 0 blocks
==12345==    still reachable: 0 bytes in 0 blocks

"Definitely lost" means the pointer to that memory is gone, and there is absolutely no way to free it now. If I scroll up, Valgrind gives me a stack trace:

==12345== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345==    at 0x4C29F73: malloc (vg_replace_malloc.c:309)
==12345==    by 0x4005B7: process_records (record_test.c:7)
==12345==    by 0x400621: main (record_test.c:23)

There it is. Line 7. That's where I allocated the records array. Valgrind is telling me that while I freed the contents of the array (the strings), I never freed the array itself.

Fixing the Leak and Verifying the Clean Slate

Now I'll go back and add free(records); after my cleanup loop. It's a simple fix, but this is exactly how I catch bugs in larger projects where the allocation might happen in one file and the free is supposed to happen in another.

After recompiling and running Valgrind again, I'm looking for the "Golden Ticket" of C programming:

All heap blocks were freed -- no leaks are possible
ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)

When you see that, you can actually sleep at night.




📋 Practical Task

Exercise: Fixing the Leaky User Directory

You've been handed a piece of code that manages a "User Directory." The program allocates a structure for a user and a dynamic string for their username, but it's currently leaking memory. Your goal is to use Valgrind to identify the leak and fix it.

The Code:

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

typedef struct {
    char *username;
    int id;
} User;

void create_user(int id, const char *name) {
    User *u = malloc(sizeof(User));
    u->id = id;
    u->username = malloc(strlen(name) + 1);
    strcpy(u->username, name);
    
    printf("User %d (%s) created.\n", u->id, u->username);
    
    // The developer attempted to clean up here
    free(u); 
}

int main() {
    create_user(101, "dev_mentor");
    create_user(102, "c_student");
    return 0;
}

Your Task:

  1. Compile the program with -g.
  2. Run it through Valgrind with --leak-check=full.
  3. Identify which specific allocation is being "definitely lost."
  4. Modify the create_user function so that Valgrind reports "All heap blocks were freed."
Rating
0 0

There are no comments for now.

to be the first to leave a comment.