CS 70

Using Valgrind

Valgrind is a tool for detecting memory errors such as leaks or double deletes in your code. It works by wrapping around your code and intercepting its interactions with the real machine. As a result, it can monitor every memory access (both reads and writes), and every allocation and deallocation of memory as it happens during your program's run.

There are other tools available that do essentially the same thing, but Valgrind is free (libré) software and works reasonably well. You'll find it installed in the CS 70 Docker container you use for development.

As with any error-detection tool, Valgrind's detection of memory issues is prone to two types of errors,

  • False Positives: The system believes it has discovered an error, but the code is actually fine. In the context of CS 70, false positives are unlikely and so you should view any error discovered by Valgrind as indicative of a problem. [Keep in mind that the location in your code where Valgrind detects an issue may or may not be the actual origin point of the problem, just the place where the problem becomes discoverable.]
  • False negatives: Even if Valgrind doesn't report any problems with your code, you can't be certain that there are no problems, just that Valgrind is unable to detect a problem. For example, Valgrind does not detect memory errors that result from accessing an out-of-bounds index on an array allocated on the stack (instead of the the heap). No automated tool can detect all errors.

What makes a tool like Valgrind useful isn’t that it's perfect, but that it can be helpful.

Practical Valgrind Usage

Valgrind analyzes your executable file, not your source code. So the first step in using it is to compile your program as you usually would, except that you must use the -g flag to tell the compiler to add additional debugging information to the object files and executable. This extra information allows Valgrind to report issues using the actual function names and line numbers from your code, which makes it a lot easier to figure things out!

Running Valgrind

As you know, If your compiled program is named programName, you would run it in the terminal by typing ./programName and pressing Return.

Running your program with Valgrind monitoring it is a bit different; for example, you might run

valgrind --leak-check=full ./programName

While your program runs, Valgrind will print its analysis to the screen, mixed in with whatever outputs your program might make, which could make it difficult for you to see what your program is doing. To clean things up, you can redirect Valgrind's results to a file by running it with a shell redirection, such as

valgrind --leak-check=full ./programName 2> valgrind.out

Here, the 2> redirects the standard error (stderr) stream to a file called valgrind.out. You can then read valgrind.out after your program finishes.

Keep in mind that if your program also prints to the standard-error stream, that output will also end up in valgrind.out.

Also, some shells are set up to protect you from accidentally overwriting existing files. That's generally a good thing, but it will trigger an error message and stop your command from running even if you know it's safe to overwrite a particular file.

It's possible to force the shell to believe that you know what you're doing and that it's okay to overwrite a given file, but exactly how you do that depends on the particular shell that you're using. You can read the manual for your shell or ask for help, but it's also pretty easy to just remove (with rm) the file you want to use before you try to redirect output to it.

How to Read and Use Valgrind’s Results

Valgrind’s results look something like

==15087== Memcheck, a memory error detector
==15087== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==15087== Using Valgrind-3.9.0 and LibVEX; rerun with -h for copyright info
==15087== Command: ./programName
==15087==
==15087==
==15087== HEAP SUMMARY:
==15087==     in use at exit: 8 bytes in 1 blocks
==15087==   total heap usage: 1 allocs, 0 frees, 8 bytes allocated
==15087==
==15087== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1
==15087==    at 0x4C2A6B0: operator new[](unsigned long) (vg_replace_malloc.c:384)
==15087==    by 0x400954: main (programName.cpp:8)
==15087==
==15087== LEAK SUMMARY:
==15087==    definitely lost: 8 bytes in 1 blocks
==15087==    indirectly lost: 0 bytes in 0 blocks
==15087==      possibly lost: 0 bytes in 0 blocks
==15087==    still reachable: 0 bytes in 0 blocks
==15087==         suppressed: 0 bytes in 0 blocks
==15087==
==15087== For counts of detected and suppressed errors, rerun with: -v
==15087== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 1 from 1)

To make sense of the results, first look at the LEAK SUMMARY portion. If it says that you have bytes that were “definitely lost”, you almost certainly have a leak. If it says that 0 bytes were “definitely lost”, you may not have a leak, but you should check the report for other errors.

Read through the middle of the report (after the Command: ./programName part). _Look for a filename that contains your code._If Valgrind finds an error in your code, it will identify specific lines where the problem occurred (e.g., where the leaked memory was allocated, or where unallocated memory was written). Look at those lines in your code and see if you can spot the problem.

Keep in mind that sometimes the error Valgrind detects is only triggered by the line of code that it calls out; the true cause of the error may lie elsewhere, but knowing where the issue appears can help you trace back to the actual bug.

Also, you may see that Valgrind reports errors related to files in the standard library that your code interacts with. While it's tempting to blame problems on the standard-library code, given the extensive testing and refinement the library code has undergone over years (or decades) of use, any problems Valgrind reports in standard-library code are almost certainly caused by something in your code that is triggered when your code calls some code in the standard libraries. Knowing what library code is affected should help you track down the real bug, since you know what library routines your code calls.

There's much more information about how Valgrind works, how to interpret its results, and more in various documents available on the internet.

Some useful references include

(When logged in, completion status appears here.)