Valgrind

Introduction

Although C is a very useful and powerful language, it can be hard to debug. A particular problem that you have probably encountered at some point is memory errors. We have already talked about gdb, which can be a helpful resource if your program consistently crashes or outputs a wrong result. However, sometimes you suspect that the problem you are having is due to a memory error, but it does not cause a segfault and you do not want to set a lot of breakpoints and step through in gdb. Another common problem you might encounter is a program with a memory leak: somewhere, you call malloc but never call free. Valgrind is a program that will help you fix both problems.

Valgrind is installed on the department machines. To invoke it on an executable called a.out, you simply run the command valgrind ./a.out (with any arguments your program might need). As when using gdb, you will want to make sure to compile your program with the flag -g, so that you can see line numbers in the output. You may also wish to debug with optimizations turned off (-O0), since if you have them on, line numbers may be inaccurate and you may occasionally encounter false alarms.

Example 1: reading/writing past the end of an array

One common mistake is accessing elements past the end of an array. Unlike java, which will throw an exception when you try that, your C program might segfault, or it might continue running, producing a result which is correct or incorrect -- sometimes with results varying between executions. This makes it hard to locate this kind of problem. Here is how you would use valgrind to find the bug:

Example 1Output
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv){
  int i;
  int *a = malloc(sizeof(int) * 10);
  if (!a) return -1; /*malloc failed*/
  for (i = 0; i < 11; i++){
    a[i] = i;
  }
  free(a);
  return 0;
}
$ gcc -Wall -pedantic -g example1.c -o example
$ valgrind ./example
==23779== Memcheck, a memory error detector
==23779== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==23779== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==23779== Command: ./example
==23779== 
==23779== Invalid write of size 4
==23779==    at 0x400548: main (example1.c:9)
==23779==  Address 0x4c30068 is 0 bytes after a block of size 40 alloc'd
==23779==    at 0x4A05E46: malloc (vg_replace_malloc.c:195)
==23779==    by 0x40051C: main (example1.c:6)
==23779== 
==23779== 
==23779== HEAP SUMMARY:
==23779==     in use at exit: 0 bytes in 0 blocks
==23779==   total heap usage: 1 allocs, 1 frees, 40 bytes allocated
==23779== 
==23779== All heap blocks were freed -- no leaks are possible
==23779== 
==23779== For counts of detected and suppressed errors, rerun with: -v
==23779== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)

Your output might be slightly different, depending on the machine and version of valgrind and libraries installed, but should include the same types of errors. If you examine the output, you will see that there is 1 error listed (you do not need to worry about suppressed errors). Valgrind prints what the error was (Invalid write of size 4) as well as the stack. It also lists the file, function and line where this array was malloc'd.

Example 2: reading uninitialized memory

Another common problem is forgetting to initialize a variable or array before using it.

Example 2Output
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv){
  int i;
  int a[10];
  for (i = 0; i < 9; i++)
    a[i] = i;
    
  for (i = 0; i < 10; i++){
    printf("%d ", a[i]);
  }
  printf("\n");
  return 0;
}
$ gcc -Wall -pedantic -g example2.c -o example2
$ valgrind ./example2
==24599== Memcheck, a memory error detector
==24599== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==24599== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==24599== Command: ./example2
==24599== 
==24599== Conditional jump or move depends on uninitialised value(s)
==24599==    at 0x33A8648196: vfprintf (in /lib64/libc-2.13.so)
==24599==    by 0x33A864FB59: printf (in /lib64/libc-2.13.so)
==24599==    by 0x400567: main (example2.c:11)
==24599== 
==24599== Use of uninitialised value of size 8
==24599==    at 0x33A864484B: _itoa_word (in /lib64/libc-2.13.so)
==24599==    by 0x33A8646D50: vfprintf (in /lib64/libc-2.13.so)
==24599==    by 0x33A864FB59: printf (in /lib64/libc-2.13.so)
==24599==    by 0x400567: main (example2.c:11)
==24599== 
==24599== Conditional jump or move depends on uninitialised value(s)
==24599==    at 0x33A8644855: _itoa_word (in /lib64/libc-2.13.so)
==24599==    by 0x33A8646D50: vfprintf (in /lib64/libc-2.13.so)
==24599==    by 0x33A864FB59: printf (in /lib64/libc-2.13.so)
==24599==    by 0x400567: main (example2.c:11)
==24599== 
0 1 2 3 4 5 6 7 8 7 
==24599== 
==24599== HEAP SUMMARY:
==24599==     in use at exit: 0 bytes in 0 blocks
==24599==   total heap usage: 0 allocs, 0 frees, 0 bytes allocated
==24599== 
==24599== All heap blocks were freed -- no leaks are possible
==24599== 
==24599== For counts of detected and suppressed errors, rerun with: -v
==24599== Use --track-origins=yes to see where uninitialised values come from
==24599== ERROR SUMMARY: 3 errors from 3 contexts (suppressed: 6 from 6)

Observe that the output of the program and the output of valgrind are interleaved; to get around that, it is handy to redirect the output to a separate file. This time the errors reported are for uninitialized values, and valgrind indicates where the access takes place (line 11 of example2.c). If you run with the option --track-origins=yes, valgrind will give additional information about where the uninitialized values came from.

Example 3: memory leaks

Valgrind includes an option to check for memory leaks. With no option given, it will list a heap summary where it will say if there is any memory that has been allocated but not freed. If you use the option --leak-check=full it will give more information.

Example 3Output
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv){
  int i;
  int *a;

  for (i=0; i < 10; i++){
    a = malloc(sizeof(int) * 100);
  }
  free(a);
  return 0;
}
$ gcc -Wall -pedantic -g example3.c -o example3
$ valgrind --leak-check=full ./example3
==24810== Memcheck, a memory error detector
==24810== Copyright (C) 2002-2009, and GNU GPL'd, by Julian Seward et al.
==24810== Using Valgrind-3.5.0 and LibVEX; rerun with -h for copyright info
==24810== Command: ./example3
==24810== 
==24810== 
==24810== HEAP SUMMARY:
==24810==     in use at exit: 3,600 bytes in 9 blocks
==24810==   total heap usage: 10 allocs, 1 frees, 4,000 bytes allocated
==24810== 
==24810== 3,600 bytes in 9 blocks are definitely lost in loss record 1 of 1
==24810==    at 0x4A05E46: malloc (vg_replace_malloc.c:195)
==24810==    by 0x400525: main (example3.c:9)
==24810== 
==24810== LEAK SUMMARY:
==24810==    definitely lost: 3,600 bytes in 9 blocks
==24810==    indirectly lost: 0 bytes in 0 blocks
==24810==      possibly lost: 0 bytes in 0 blocks
==24810==    still reachable: 0 bytes in 0 blocks
==24810==         suppressed: 0 bytes in 0 blocks
==24810== 
==24810== For counts of detected and suppressed errors, rerun with: -v
==24810== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 6 from 6)

If you see leaks indicated as still reachable, this generally does not indicate a serious problem since the memory was probably still in use at the end of the program. However, any leaks listed as "definitely lost" should be fixed (as should ones listed "indirectly lost" or "possibly lost" -- "indirectly lost" will happen if you do something like free the root node of a tree but not the rest of it, and "possibly lost" generally indicates the memory is actually lost). An example of where you might run into an example like the one above is if you have a function that allocates a buffer (perhaps to store a string) and returns it, but the caller never frees the memory after it is finished. If a program like that runs for a long time, it will allocate a lot of memory that it does not need.

What valgrind is NOT

Although valgrind is an extremely useful program, it will not miraculously tell you about every memory bug in your program. There are several limitations that you should keep in mind. It does not do bounds checking on stack/static arrays (those not allocated with malloc), so it is possible to have a program that behaves badly while not generating any valgrind errors. For example:

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

int main(int argc, char** argv){
  int i;
  int x =0;
  int a[10];
  for (i = 0; i < 11; i++)
    a[i] = i;
    
  printf("x is %d\n", x);
  return 0;
}

This program has a memory error, resulting in the value of x being 10 at the end rather than 0. However, valgrind will not report any errors.

A more serious limitation that you will encounter is that valgrind checks programs dynamically -- that is, it checks during actual program execution whether any leaks actually occurred for that execution. This means that if you run valgrind on a particular set of inputs that does not cause any bad memory accesses or memory to be leaked, valgrind will not report any errors, even though your program does contain bugs. As an example:

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

int main(int argc, char** argv){
  char *str = malloc(10);
  gets(str);
  printf("%s\n",str);

  return 0;
}

This program has a bug: if the user inputs a long string, the buffer str will overflow. Please note that you should never, ever use gets, for exactly this reason. If you run this program through valgrind, you will get a memory error if you type in a string longer than 10 characters. However, if you type in a shorter string, valgrind will report no errors, even though the program is buggy! If you want to be reasonably sure that you are catching all memory bugs, you should run valgrind on a variety of inputs, especially corner cases, as those are where you are most likely to have made a mistake like accessing past the bounds of an array.

When fixing errors, it is a good idea to start at the top; fixing an error that occurs earlier is likely to eliminate a lot of later errors as well.

Once in a great while you may encounter a false positive -- an error even though there is nothing wrong with your program. However, in the vast majority of cases, any error reported is real and you should fix it. Be very wary about dismissing an error as a "false positive," since it is much more likely that you have made a mistake.

One final thing to note about valgrind is that your programs will take longer to execute (like 20 to 30 times as long), and will also use more memory.

More information

If you are curious about valgrind, you can check the valgrind website, especially the FAQ.

Please send any corrections or questions to lena@cs. Last updated 16 March 2011.