Let's take a simple program that has some memory leaks and explore how the Valgrind tool, with the help of Memcheck, can help us detect memory leaks. As Memcheck is the default tool used by Valgrind, it is not necessary to explicitly call out the Memcheck tool while issuing the Valgrind command:
valgrind application_debugged.exe --tool=memcheck
The following code implements a singly linked list:
#include <iostream>
using namespace std;
struct Node {
int data;
Node *next;
};
class List {
private:
Node *pNewNode;
Node *pHead;
Node *pTail;
int __size;
void createNewNode( int );
public:
List();
~List();
int size();
void append ( int data );
void print( );
};
As you may have observed, the preceding class declaration has methods to append() a new node, print() the list, and a size() method that returns the number of nodes in the list.
Let's explore the list.cpp source file that implements the append() method, the print() method, the constructor, and the destructor:
#include "list.h"
List::List( ) {
pNewNode = NULL;
pHead = NULL;
pTail = NULL;
__size = 0;
}
List::~List() {}
void List::createNewNode( int data ) {
pNewNode = new Node();
pNewNode->next = NULL;
pNewNode->data = data;
}
void List::append( int data ) {
createNewNode( data );
if ( pHead == NULL ) {
pHead = pNewNode;
pTail = pNewNode;
__size = 1;
}
else {
Node *pCurrentNode = pHead;
while ( pCurrentNode != NULL ) {
if ( pCurrentNode->next == NULL ) break;
pCurrentNode = pCurrentNode->next;
}
pCurrentNode->next = pNewNode;
++__size;
}
}
void List::print( ) {
cout << "\nList entries are ..." << endl;
Node *pCurrentNode = pHead;
while ( pCurrentNode != NULL ) {
cout << pCurrentNode->data << "\t";
pCurrentNode = pCurrentNode->next;
}
cout << endl;
}
The following code demonstrates the main() function:
#include "list.h"
int main ( ) {
List l;
for (int count = 0; count < 5; ++count )
l.append ( (count+1) * 10 );
l.print();
return 0;
}
Let's compile the program and attempt to detect memory leaks in the preceding program:
g++ main.cpp list.cpp -std=c++17 -g
valgrind ./a.out --leak-check=full
==99789== Memcheck, a memory error detector
==99789== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==99789== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==99789== Command: ./a.out --leak-check=full
==99789==
List constructor invoked ...
List entries are ...
10 20 30 40 50
==99789==
==99789== HEAP SUMMARY:
==99789== in use at exit: 72,784 bytes in 6 blocks
==99789== total heap usage: 7 allocs, 1 frees, 73,808 bytes allocated
==99789==
==99789== LEAK SUMMARY:
==99789== definitely lost: 16 bytes in 1 blocks
==99789== indirectly lost: 64 bytes in 4 blocks
==99789== possibly lost: 0 bytes in 0 blocks
==99789== still reachable: 72,704 bytes in 1 blocks
==99789== suppressed: 0 bytes in 0 blocks
==99789== Rerun with --leak-check=full to see details of leaked memory
==99789==
==99789== For counts of detected and suppressed errors, rerun with: -v
==99789== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
From the preceding output, it is evident that 80 bytes are leaked by our application. While definitely lost and indirectly lost indicate the memory leaked by our application, still reachable does not necessarily indicate our application, and it could be leaked by third-party libraries or C++ runtime libraries. It may be possible that they are not really memory leaks, as the C++ runtime library might use memory pools.