Valgrind

Memory Leaks

What’s wrong with the following program, leaky.c?

#include <stdio.h>
int main() {
    char *x = (char *) malloc(sizeof(char) * 10);
    return 0;
}

This program, quite obviously, demonstrates a basic memory leak. A memory leak is when allocated memory is not freed. Unlike in Java, which does garbage collection for you, C and C++ do not. This means that you as the programmer have to keep track of everything that’s been dynamically allocated. How do we fix this? Quite simple, in this example.

#include <stdio.h>
int main() {
    char *x = (char *) malloc(sizeof(char) * 10);
    free(x);
    return 0;
}

Great - now everything that’s been allocated has been freed. But there should be an automatic way to do this right? I mean, what are you guys going to do in the crawler? You have three data structures - what if you forgot to free just one of those nodes? What happenes when you’re leaking memory for hours and hours?

Enter Valgrind, the automated way to find memory leaks.

Let’s run Valgrind on the above example. Oh right, installation. If you’re on a Mac, brew install valgrind will suffice. If you’re on Ubuntu, sudo apt-get install valgrind. Now, we simply gcc leaky.c, which produces our familiar executable a.out. To run valgrind, we simply run valgrind --tool=memcheck --leak-check=yes ./a.out.

This produces a lot of output, but the snippet we care about is the summary statistics at the bottom.

==46250== LEAK SUMMARY:
==46250==    definitely lost: 10 bytes in 1 blocks
==46250==    indirectly lost: 0 bytes in 0 blocks
==46250==      possibly lost: 0 bytes in 0 blocks
==46250==    still reachable: 0 bytes in 0 blocks
==46250==         suppressed: 25,198 bytes in 375 blocks
==46250==
==46250== For counts of detected and suppressed errors, rerun with: -v
==46250== Use --track-origins=yes to see where uninitialised values come from
==46250== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 150 from 50)

From this, we can see that you can actually run valgrind with a verbose (-v) option, which will help you identify any particularly tricky errors. I suggest getting used to valgrind quickly, as all of your labs from now on must be without memory leaks.

There are a couple other use cases for valgrind, too.

Invalid Pointer Use

Detecting Uninitialized Variables

Here’s a simple example.

#include <stdio.h>

int main() {
    int x;
    if (x == 0)
        printf("x is totally less than 0\n");
    
    return 0;
}

Or a more complex one that valgrind will also catch.

#include <stdio.h>

int foo(int x) {
    if (x < 10)
        printf("x is less than 10\n");
}

int main() {
    int y;
    foo(y);
}

What Else

Invalid / mismatched frees! This is a lifesaver. Imagine you have a linked list, and you’re iterating over it and freeing nodes along the way when – oops! You freed something twice. Or perhaps, you freed something what wasn’t allocated. Valgrind can warn you of these things.

Caveats Re Valgrind

Valgrind is far from a perfect tool. There are a few things to keep in mind:

Example:

int main() {
    char x[10];
    x[11] = 'a';
}

Valgrind won’t complain about the above at all. Why not? Some of you might think it contradicts the above example for “invalid pointer use”, but you’ll notice that this is not a pointer; it’s statically allocated here.

Resources

Want to learn more about valgrind? Here are some other great reads.

C Programming is always a trusted source. You should also check out their pre-processing directives.

Full documentation of valgrind - probably more in depth than you’d need.

Very basic, probably not the most useful.