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.1
program memleak
implicit none
call foo()
contains
subroutine foo
integer, dimension(:), pointer :: 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 finds two error: the “invalid write” on line 13 and the
allocated memory on line 11.
It notes that there is one block of 40 bytes in use at exit.
==30906== Memcheck, a memory error detector
==30906== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
==30906== Using Valgrind-3.10.0 and LibVEX; rerun with -h for copyright info
==30906== Command: ./memleak
==30906==
==30906== Invalid write of size 4
==30906== at 0x4007B3: foo.3416 (memleak.f90:13)
==30906== by 0x4007D8: MAIN__ (memleak.f90:4)
==30906== by 0x40080E: main (memleak.f90:4)
==30906== Address 0x5c5f458 is 0 bytes after a block of size 40 alloc'd
==30906== at 0x4C28C20: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==30906== by 0x40075C: foo.3416 (memleak.f90:11)
==30906== by 0x4007D8: MAIN__ (memleak.f90:4)
==30906== by 0x40080E: main (memleak.f90:4)
==30906==
==30906==
==30906== HEAP SUMMARY:
==30906== in use at exit: 40 bytes in 1 blocks
==30906== total heap usage: 20 allocs, 19 frees, 12,000 bytes allocated
==30906==
==30906== LEAK SUMMARY:
==30906== definitely lost: 40 bytes in 1 blocks
==30906== indirectly lost: 0 bytes in 0 blocks
==30906== possibly lost: 0 bytes in 0 blocks
==30906== still reachable: 0 bytes in 0 blocks
==30906== suppressed: 0 bytes in 0 blocks
==30906== Rerun with --leak-check=full to see details of leaked memory
==30906==
==30906== For counts of detected and suppressed errors, rerun with: -v
==30906== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
To provide an example of a memory leak in C, consider instead the
memleak2
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.
-
I thank Kay Diederichs for pointing out an error in the original version of this example from December 2010. If the array
x
is declaredallocatable
instead ofpointer
, indeed the memory would be freed upon return from the subroutine. ↩