The cs-intro.ua.edu
and cs-parallel.ua.edu
testing servers now come with the utility Valgrind preinstalled.
Valgrind is a utility which is able to detect memory leaks and provide information about memory-related errors (segmentation faults).
Valgrind supports a small set of linux-based operating systems and, in most cases, can still be rather difficult. Therefore, the server's version is extremely helpful for testing.
This guide will show how to use Valgrind.
The following sample code will be used to demonstrate some of valgrind's features. It is C++ code, however, the same works for C (malloc
/free
).
#include <stdio.h>
using namespace std;
void sampleSegfault() {
int foo[2];
foo[999999] = 12; // out of bounds
}
void sampleUseWithoutAlloc() {
int *foo;
*foo = 2;
}
void sampleDoubleFree() {
int *foo = new int;
delete foo;
delete foo;
}
void sampleUseAfterFree() {
int *foo = new int[3];
delete[] foo;
foo[0] = 12;
}
void sampleMemoryLeakOne() {
int *foo = new int;
}
void sampleMemoryLeakTwo() {
int *foo = new int;
foo = new int;
delete foo;
}
void sampleFopen() {
FILE *cStyle = fopen("test.txt", "r");
}
int main(int argc, char **argv) {
switch (argv[1][0]) {
case '0':
sampleSegfault();
return 0;
case '1':
sampleUseWithoutAlloc();
return 0;
case '2':
sampleDoubleFree();
return 0;
case '3':
sampleUseAfterFree();
return 0;
case '4':
sampleMemoryLeakOne();
sampleMemoryLeakTwo();
return 0;
case '5':
sampleFopen();
return 0;
}
}
To compile your code so that Valgrind can provide information about segmentation faults, add the -g
flag to g++
or gcc
. If there are other options on gcc
/g++
, such as -std=c++11
or -Wall
, add it to this list.
For example, g++ -Wall -std=c++11 test.cpp -o ./compiled
becomes g++ -Wall -std=c++11 -g test.cpp -o ./compiled
.
For the above code, I used g++ -g memory-test.cpp
.
To run Valgrind, add valgrind --leak-check=full --show-leak-kinds=all
before the normal command.
./a.out
would become valgrind --leak-check=full --show-leak-kinds=all ./a.out
. To provide arguments to the program, put them at the end and, as usual, argv
will start with the comamnd name (./a.out
). For example, valgrind --leak-check=full --show-leak-kinds=all ./a.out I_am_argv_1 I_am_argv_2 I_am_argv_3
.
valgrind --leak-check=full --show-leak-kinds=all ./a.out 0
==13402== Memcheck, a memory error detector
==13402== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==13402== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==13402== Command: ./a.out 0
==13402==
==13402== Invalid write of size 4
==13402== at 0x4006D1: sampleSegfault() (memory-test.cpp:6)
==13402== by 0x4007E1: main (memory-test.cpp:42)
==13402== Address 0x1fff3d0b9c is not stack'd, malloc'd or (recently) free'd
==13402==
==13402==
==13402== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==13402== Access not within mapped region at address 0x1FFF3D0B9C
==13402== at 0x4006D1: sampleSegfault() (memory-test.cpp:6)
==13402== by 0x4007E1: main (memory-test.cpp:42)
==13402== If you believe this happened as a result of a stack
==13402== overflow in your program's main thread (unlikely but
==13402== possible), you can try to increase the size of the
==13402== main thread stack using the --main-stacksize= flag.
==13402== The main thread stack size used in this run was 8388608.
==13402==
==13402== HEAP SUMMARY:
==13402== in use at exit: 0 bytes in 0 blocks
==13402== total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==13402==
==13402== All heap blocks were freed -- no leaks are possible
==13402==
==13402== For lists of detected and suppressed errors, rerun with: -s
==13402== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)
Here, we can see that the output tells us that the program tried writing to an address which was not allocated and was not freed (recently). Based on the stack trace, we can see line 42 (in function main
) called sampleSegfault()
and that the violation occured on line 6.
For reference, sampleSegfault
:
void sampleSegfault() {
int foo[2];
foo[3] = 12; // out of bounds
}
As the array was created on the stack, it is automatically deleted/freed at the function's exit. Therefore, Valgrind is correct in that all allocations were freed.
valgrind --leak-check=full --show-leak-kinds=all ./a.out 1
==14550== Memcheck, a memory error detector
==14550== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14550== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14550== Command: ./a.out 1
==14550==
==14550== Use of uninitialised value of size 8
==14550== at 0x4006E5: sampleUseWithoutAlloc() (memory-test.cpp:11)
==14550== by 0x4007FD: main (memory-test.cpp:45)
==14550==
==14550== Invalid write of size 4
==14550== at 0x4006E5: sampleUseWithoutAlloc() (memory-test.cpp:11)
==14550== by 0x4007FD: main (memory-test.cpp:45)
==14550== Address 0x0 is not stack'd, malloc'd or (recently) free'd
==14550==
==14550==
==14550== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==14550== Access not within mapped region at address 0x0
==14550== at 0x4006E5: sampleUseWithoutAlloc() (memory-test.cpp:11)
==14550== by 0x4007FD: main (memory-test.cpp:45)
==14550== If you believe this happened as a result of a stack
==14550== overflow in your program's main thread (unlikely but
==14550== possible), you can try to increase the size of the
==14550== main thread stack using the --main-stacksize= flag.
==14550== The main thread stack size used in this run was 8388608.
==14550==
==14550== HEAP SUMMARY:
==14550== in use at exit: 0 bytes in 0 blocks
==14550== total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==14550==
==14550== All heap blocks were freed -- no leaks are possible
==14550==
==14550== Use --track-origins=yes to see where uninitialised values come from
==14550== For lists of detected and suppressed errors, rerun with: -s
==14550== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Segmentation fault (core dumped)
Here, Valgrind says that there was a "use of an uninitialized value" on line 11 in sampleUseWithoutAlloc
.
We can see that sampleUseWithoutAlloc
attempts to use foo
before creating it (so it is undefined):
void sampleUseWithoutAlloc() {
int *foo;
*foo = 2;
}
valgrind --leak-check=full --show-leak-kinds=all ./a.out 2
==14567== Memcheck, a memory error detector
==14567== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14567== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14567== Command: ./a.out 2
==14567==
==14567== Invalid free() / delete / delete[] / realloc()
==14567== at 0x4C2B9DB: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14567== by 0x40071A: sampleDoubleFree() (memory-test.cpp:17)
==14567== by 0x400809: main (memory-test.cpp:48)
==14567== Address 0x5ae3c80 is 0 bytes inside a block of size 4 free'd
==14567== at 0x4C2B9DB: operator delete(void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14567== by 0x40070E: sampleDoubleFree() (memory-test.cpp:16)
==14567== by 0x400809: main (memory-test.cpp:48)
==14567== Block was alloc'd at
==14567== at 0x4C2A91F: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14567== by 0x4006FE: sampleDoubleFree() (memory-test.cpp:15)
==14567== by 0x400809: main (memory-test.cpp:48)
==14567==
==14567==
==14567== HEAP SUMMARY:
==14567== in use at exit: 0 bytes in 0 blocks
==14567== total heap usage: 2 allocs, 3 frees, 72,708 bytes allocated
==14567==
==14567== All heap blocks were freed -- no leaks are possible
==14567==
==14567== For lists of detected and suppressed errors, rerun with: -s
==14567== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
This is an example of an issue which would cause an abort. Valgrind tells us that there was an "invalid free() / delete / delete[] / realloc()". In addition to this, it tells us many useful pieces of information:
- The invalid free occurred at line 17
- The block was originally freed on line 16
- The block was allocated (created) with new on line 15
In addition to this, we can see in the summary that there was one too many frees issued.
This one is similar to the previous example.
valgrind --leak-check=full --show-leak-kinds=all ./a.out 3
==14602== Memcheck, a memory error detector
==14602== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14602== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14602== Command: ./a.out 3
==14602==
==14602== Invalid write of size 4
==14602== at 0x40074A: sampleUseAfterFree() (memory-test.cpp:23)
==14602== by 0x400815: main (memory-test.cpp:51)
==14602== Address 0x5ae3c80 is 0 bytes inside a block of size 12 free'd
==14602== at 0x4C2C0BB: operator delete[](void*) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14602== by 0x400745: sampleUseAfterFree() (memory-test.cpp:22)
==14602== by 0x400815: main (memory-test.cpp:51)
==14602== Block was alloc'd at
==14602== at 0x4C2B03F: operator new[](unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14602== by 0x40072E: sampleUseAfterFree() (memory-test.cpp:21)
==14602== by 0x400815: main (memory-test.cpp:51)
==14602==
==14602==
==14602== HEAP SUMMARY:
==14602== in use at exit: 0 bytes in 0 blocks
==14602== total heap usage: 2 allocs, 2 frees, 72,716 bytes allocated
==14602==
==14602== All heap blocks were freed -- no leaks are possible
==14602==
==14602== For lists of detected and suppressed errors, rerun with: -s
==14602== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
In this, we see that the program attempted to write inside a block which was freed.
valgrind --leak-check=full --show-leak-kinds=all ./a.out 4
==14608== Memcheck, a memory error detector
==14608== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14608== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14608== Command: ./a.out 4
==14608==
==14608==
==14608== HEAP SUMMARY:
==14608== in use at exit: 8 bytes in 2 blocks
==14608== total heap usage: 4 allocs, 2 frees, 72,716 bytes allocated
==14608==
==14608== 4 bytes in 1 blocks are definitely lost in loss record 1 of 2
==14608== at 0x4C2A91F: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14608== by 0x400763: sampleMemoryLeakOne() (memory-test.cpp:27)
==14608== by 0x400821: main (memory-test.cpp:54)
==14608==
==14608== 4 bytes in 1 blocks are definitely lost in loss record 2 of 2
==14608== at 0x4C2A91F: operator new(unsigned long) (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14608== by 0x40077B: sampleMemoryLeakTwo() (memory-test.cpp:30)
==14608== by 0x400826: main (memory-test.cpp:55)
==14608==
==14608== LEAK SUMMARY:
==14608== definitely lost: 8 bytes in 2 blocks
==14608== indirectly lost: 0 bytes in 0 blocks
==14608== possibly lost: 0 bytes in 0 blocks
==14608== still reachable: 0 bytes in 0 blocks
==14608== suppressed: 0 bytes in 0 blocks
==14608==
==14608== For lists of detected and suppressed errors, rerun with: -s
==14608== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
In this example, sampleMemoryLeakOne
and sampleMemoryLeakTwo
both leak memory. Valgrind tells us about each of these leaks and where the relevant memory was allocated.
Valgrind has a few different types of memory leak classifications. Here are what the most common mean:
- Definitely lost refers to memory that has directly been lost, such as in the above example where the memory is allocated and, once the function is returned, the pointers referring to this memory are lost. For memory to be directly lost, every pointer pointing to the memory has to be lost.
- Indirectly lost refers to memory that still has pointers pointing to it, however, cannot be reached from the main program. For example, if a linked list's head is lost, the head itself would be directly lost. However, all of its children would be indirectly lost as a result of the head being lost.
- Still reachable referes to memory which is still being pointed to by some code (99% of the time internal parts of standard library, such as file pointers managed by C/C++) at the program exit
valgrind --leak-check=full --show-leak-kinds=all ./a.out 6
==14671== Memcheck, a memory error detector
==14671== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14671== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14671== Command: ./a.out 6
==14671==
==14671==
==14671== HEAP SUMMARY:
==14671== in use at exit: 0 bytes in 0 blocks
==14671== total heap usage: 1 allocs, 1 frees, 72,704 bytes allocated
==14671==
==14671== All heap blocks were freed -- no leaks are possible
==14671==
==14671== For lists of detected and suppressed errors, rerun with: -s
==14671== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
ncovercash@cs-intro:~> valgrind --leak-check=full --show-leak-kinds=all ./a.out 5
==14673== Memcheck, a memory error detector
==14673== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==14673== Using Valgrind-3.15.0 and LibVEX; rerun with -h for copyright info
==14673== Command: ./a.out 5
==14673==
==14673==
==14673== HEAP SUMMARY:
==14673== in use at exit: 552 bytes in 1 blocks
==14673== total heap usage: 2 allocs, 1 frees, 73,256 bytes allocated
==14673==
==14673== 552 bytes in 1 blocks are still reachable in loss record 1 of 1
==14673== at 0x4C2A2AF: malloc (in /usr/lib64/valgrind/vgpreload_memcheck-amd64-linux.so)
==14673== by 0x579B3DC: __fopen_internal (in /lib64/libc-2.22.so)
==14673== by 0x4007B2: sampleFopen() (memory-test.cpp:36)
==14673== by 0x400832: main (memory-test.cpp:58)
==14673==
==14673== LEAK SUMMARY:
==14673== definitely lost: 0 bytes in 0 blocks
==14673== indirectly lost: 0 bytes in 0 blocks
==14673== possibly lost: 0 bytes in 0 blocks
==14673== still reachable: 552 bytes in 1 blocks
==14673== suppressed: 0 bytes in 0 blocks
==14673==
==14673== For lists of detected and suppressed errors, rerun with: -s
==14673== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Here, we see that sampleFopen
resulted in calling fopen
. As a result of fopen
, the internal parts of C allocated some related memory to keep track of the file/file pointer. Since there was never a corresponding fclose
, it is still here at the end of the program.