Debugging Memory Issues with Valgrind

December 22, 2010

The simple Fortran 90 program below, memleak, contains two memory errors. The upper bound of the array is exceeded by the assignment to x(11) and the array x is allocated, but never deallocated, resulting in a memory leak.

program memleak
  implicit none

  call foo()

contains

  subroutine foo
    integer, dimension(:), allocatable :: x

    allocate(x(10))

    x(11) = 0         ! heap block overrun
    return            ! x not deallocated
  end subroutine foo

end program memleak

Compiling this program with gfortran and running it on GNU/Linux results in the following error:

% gfortran -o memleak memleak.f90
% ./memleak
*** glibc detected *** ./memleak: free(): invalid next size (fast): 0x00000000006fd970 ***

The first memory bug could have been detected by the compiler using the -fbounds-check flag:

% gfortran -fbounds-check -o memleak memleak.f90
% ./memleak
At line 13 of file memleak.f90
Fortran runtime error: Array reference out of bounds for array 'x',
upper bound of dimension 1 exceeded (11 > 10)

However, the second bug is a bit more evasive. In these situations, a specialized tool for diagnosing memory issues such as Valgrind can help.

To use Valgrind an executable must be produced with debugging enabled via the -g flag after which valgrind can be called:

% gfortran -g -o memleak memleak.f90
% valgrind ./memleak

If there are no problems, Valgrind will report something like the following:

==14364==
==14364== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 8 from 1)
==14364== malloc/free: in use at exit: 0 bytes in 0 blocks.
==14364== malloc/free: 225,138 allocs, 225,138 frees, 513,185,927 bytes allocated.
==14364== For counts of detected errors, rerun with: -v
==14364== All heap blocks were freed -- no leaks are possible.

The Valgrind output for the memleak program is shown below. Notice that Valgrind only finds one error: the “invalid write” on line 13. It notes that “all heap blocks were freed.” Apparently the compiler has found and fixed the second error itself.

==14418== Memcheck, a memory error detector.
==14418== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.
==14418== Using LibVEX rev 1878, a library for dynamic binary translation.
==14418== Copyright (C) 2004-2008, and GNU GPL'd, by OpenWorks LLP.
==14418== Using valgrind-3.4.0-Debian, a dynamic binary instrumentation framework.
==14418== Copyright (C) 2000-2008, and GNU GPL'd, by Julian Seward et al.
==14418== For more details, rerun with: -v
==14418==
==14418== Invalid write of size 4
==14418==    at 0x4007CE: foo.1532 (memleak.f90:13)
==14418==    by 0x40082B: MAIN__ (memleak.f90:4)
==14418==    by 0x400859: main (fmain.c:21)
==14418==  Address 0x5907b88 is 0 bytes after a block of size 40 alloc'd
==14418==    at 0x4C2291E: malloc (vg_replace_malloc.c:207)
==14418==    by 0x40078E: foo.1532 (memleak.f90:11)
==14418==    by 0x40082B: MAIN__ (memleak.f90:4)
==14418==    by 0x400859: main (fmain.c:21)
==14418==
==14418== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 8 from 1)
==14418== malloc/free: in use at exit: 0 bytes in 0 blocks.
==14418== malloc/free: 15 allocs, 15 frees, 26,799 bytes allocated.
==14418== For counts of detected errors, rerun with: -v
==14418== All heap blocks were freed -- no leaks are possible.

To provide a true example of a memory leak, consider instead the memleak2 C program shown below. Here we use the malloc function to allocate enough memory to store an array of integers of length ten but we never call free to release the memory. Note that the heap overrun bug is also still present.

#include <stdlib.h>

void foo(void) {
  int* x;
  x = malloc(10 * sizeof(int));
  x[10] = 0;        // heap block overrun
  return;           // x not freed
}

int main(void) {
  foo();
  return 0;
}

We compile memleak2 with the GNU C compiler using the debug flag as before and then run Valgrind, this time with --leak-check=full:

% gcc -g -o memleak2 memleak2.c
% valgrind --leak-check=full ./memleak

The Valgrind output is provided below. We see that the heap overrun in function foo on line 6 is detected and that 40 bytes of memory allocated in function foo on line 5 were not freed.

==15242== Memcheck, a memory error detector.
==15242== Copyright (C) 2002-2008, and GNU GPL'd, by Julian Seward et al.
==15242== Using LibVEX rev 1878, a library for dynamic binary translation.
==15242== Copyright (C) 2004-2008, and GNU GPL'd, by OpenWorks LLP.
==15242== Using valgrind-3.4.0-Debian, a dynamic binary instrumentation framework.
==15242== Copyright (C) 2000-2008, and GNU GPL'd, by Julian Seward et al.
==15242== For more details, rerun with: -v
==15242== 
==15242== Invalid write of size 4
==15242==    at 0x4004CA: foo (memleak2.c:6)
==15242==    by 0x4004DA: main (memleak2.c:11)
==15242==  Address 0x5179058 is 0 bytes after a block of size 40 alloc'd
==15242==    at 0x4C2291E: malloc (vg_replace_malloc.c:207)
==15242==    by 0x4004BD: foo (memleak2.c:5)
==15242==    by 0x4004DA: main (memleak2.c:11)
==15242== 
==15242== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 8 from 1)
==15242== malloc/free: in use at exit: 40 bytes in 1 blocks.
==15242== malloc/free: 1 allocs, 0 frees, 40 bytes allocated.
==15242== For counts of detected errors, rerun with: -v
==15242== searching for pointers to 1 not-freed blocks.
==15242== checked 78,144 bytes.
==15242== 
==15242== 
==15242== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==15242==    at 0x4C2291E: malloc (vg_replace_malloc.c:207)
==15242==    by 0x4004BD: foo (memleak2.c:5)
==15242==    by 0x4004DA: main (memleak2.c:11)
==15242==
==15242== LEAK SUMMARY:
==15242==    definitely lost: 40 bytes in 1 blocks.
==15242==      possibly lost: 0 bytes in 0 blocks.
==15242==    still reachable: 0 bytes in 0 blocks.
==15242==         suppressed: 0 bytes in 0 blocks.