Home

About

Blog

Projects

Fixing Memory Leaks in C with Valgrind

The most dangerous phrase in the language is, 'We’ve always done it this way'." ~ Grace Hopper

Memory leaks are a sneaky class of bugs that come about when memory is allocated for something and not reclaimed. It is hard to detect because, at a glance, your program could run just fine. No logic bugs or more critical segmentation fault type of bugs occuring that clearly crash the program and users seem to be happy with everything, reporting no issues.

While this might be the case for simpler programs or less memory-intensive programs, as memory usage increases there is a need to make sure your memory management game is tight, or else your programs can suffer from poor performance or even crash outright. Fear not! Smart programmers have built tools that we mere mortals can use to find the memory leaks in our programs and fix them. Enter:

Valgrind!

What is Valgrind and why should we use it? Valgrind is a debugging and profiling tool for C/C++ programs. It will report on the total "blocks" of memory that are not freed, and a confidence level (`definitely` vs `probably` lost) that the memory block in question is leaked and the function/line the leak occurred from (like a call stack). This is a _huge_ time savings for a programmer to find out where potential leaks lie. You still have to go in, assess those areas of the program, and work out what's wrong and how to fix it. Valgrind can't do that for you, but you'll use your time more efficiently working on fixes.

Together, we will look at an example of a memory leak from my own mini-`ls` program I was working on recently which should hopefully illuminate things.

---

Walkthrough fixing a simple leak

Valgrind can _likely_ be installed through your OS' package manager. For Fedora, it was `dnf install valgrind`.

Once installed, the first step is to compile our program with the `-g` flag to add debugging information. Our Makefile line now looks like this: `CFLAGS=-Wall -Wextra -Werror -g3 -I.`.

Next, we would rebuild our program using `make`. If you're using `gcc` or your favorite compiler directly, add the flag to the command arguments and run like usual.

**ASIDE:** Attentive readers may have noticed that my Makefile flag is `-g3`, not `-g`. What gives?! This is debugging @ "level 3" information which means we get more information, like marco expansion (if supported). Think of it like a "Gimmie all the info" flag. Debugging levels go from `-g0` (no info) to `-g3` (all the things!).

We can run it with Valgrind appended to what we normally use to run the program. For example, for our `lsls` program to list a directory (long-mode) it would normally be:

`./build/lsls -l ~/MyDocs`.

To invoke Valgrind's memory profiling for the same it would change to be:

`valgrind --leak-check=yes ./build/lsls -l ~/MyDocs`. Easy!

A Wild Memory Leak Appeared!

Now Valgrind will run our program, check for leaks, and report any that were found. Towards the bottom, there's a summary of all the leaks in a 'table'. Since the terminal dumps the table out last, it's the first thing I review to get a sense of the situation. With that, I work my way back up the output and tackle each of the individual leaks.

Here's an example of one such leak error reported (__A/N:__ you can ignore the `==######==` part of the output as that's just the process ID):

We can see that this is "definitely lost" memory, so we absolutely need to fix it. It occurs in the `entries.c` file within the function `setup_entries` on line 148. So let's jump there and take a look.

It looks like we have an issue with the `entries.targets` allocation not being freed. In this case, I discovered that, while I had called `free(entries);` elsewhere when finished with the entries, I had forgotten about the `targets` that were heap allocated. This leak was fixed by adding the line `free(entries->targets);` just before the entries themselves were freed. One down, N more to go! That's the general flow of it all, simply a matter of repeating for each leak detected.

Finally, after long last, when you've handled all of the leaks you'll see, what has been described as the most beautiful sight by weary C devs, a line towards the bottom of your summary report:

`==80305== All heap blocks were freed -- no leaks are possible`

Congrats comrade! Your program is now memory leak-free. If nothing else you'll walk away thinking more carefully about memory allocation/freeing logic in your next project upfront and save yourself trouble on the backend. I know I certainly did!

Do you use Valgrind for profiling and debugging your C/C++ programs? Do you have any tips or tricks of the trade? Maybe you have an alternative program you find works better? Let me know on Mastodon!

© 2024-present by Katie Keller.

Content of this website is licensed under CC BY-NC-SA 4.0.

Crafted with 💖. Built with gempost.