How to Use Valgrind to Check Memory in C/C++
A memory debugging tool!
We have also published about CppCheck which serves to perform static analysis.
In this article we are going to know and learn how to use Valgrind, which is a programming tool for memory debugging, memory leak detection and “profiling”.
Valgrind runs its code in a virtual machine, that is, nothing of the original program is executed directly in the processor! Instead, Valgrind first translates the program into a temporary, simpler form called an intermediate representation (IR), which is a form based on a processor-neutral static single assignment form.
Valgrind was originally designed to be a free memory debugging tool for x86 architectures, but has since evolved to become a generic framework for creating dynamic analysis tools such as checkers and profilers.
Valgrind is, in essence, a virtual machine that uses just-in-time compilation techniques, including dynamic recompilation.
Valgrind is Free Software licensed under the terms of the license: GNU GPLv2.
Installation
On Windows you will need to have MinGW installed(see here how) and then download (or compile) accordingly with this procedure.
# Debian, Ubuntu, Mint and similar
sudo apt install valgrind
# Arch, Manjaro and similar
sudo pacman -S valgrind
# Fedora and similar
sudo dnf install valgrind
# On Gentoo, Funtoo and the like
sudo emerge dev-util/valgrind
# Snap
sudo snap install valgrind --classic
Usage
Suppose you have the code below, which is a mini program that adds two numbers as parameters via the command line. The code uses Smart Pointers and everything is working normally!
Using Smart Pointers
#include <iostream>
#include <memory> // FOR SMART POINTERS
#include <algorithm>
struct Math {
private:
int num1, num2;
public:
Math(const int &inum1, const int &inum2)
: num1(inum1), num2(inum2) {}
int result(){
return num1 + num2;
}
};
bool is_number(const std::string& s){
return !s.empty() && std::find_if(s.begin(),
s.end(), [](unsigned char c) {
return !std::isdigit(c);
}) == s.end();
}
int main(int argc, char **argv){
if(argc > 2){
const std::string a = argv[1], b = argv[2];
if( !is_number(a) || !is_number(b) ){
std::cerr << "Use only numbers.\n";
return 1;
}
// USANDO SMART POINTERS
auto math = std::make_shared<Math>(
std::stoi(a),
std::stoi(b)
);
std::cout << math->result() << '\n';
}else{
std::cerr << "Use: " <<
argv[0] << " num1 num2\n";
return 1;
}
return 0;
}
After compiling, you run your code and decide to test it with Valgrind with the command and parameters:
valgrind -s --leak-check=yes ./a.out 1 2
Valgrind does not detect any failures and returns at the end of the output below: ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
, that is, no errors!
==5659== Memcheck, a memory error detector
==5659== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5659== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==5659== Command: ./a.out 1 2
==5659==
3
==5659==
==5659== HEAP SUMMARY:
==5659== in use at exit: 0 bytes in 0 blocks
==5659== total heap usage: 3 allocs, 3 frees, 73,752 bytes allocated
==5659==
==5659== All heap blocks were freed -- no leaks are possible
==5659==
==5659== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Allocating memory
Now you decide to modify your code and allocate memory with new
declaration, but forget to deallocate with delete
. Removed <memory>
header and replaced make_shared
in code below
#include <iostream>
#include <algorithm>
struct Math {
private:
int num1, num2;
public:
Math(const int &inum1, const int &inum2) : num1(inum1), num2(inum2) {}
int result() { return num1 + num2; }
};
bool is_number(const std::string &s) {
return !s.empty() && std::find_if(s.begin(), s.end(), [](unsigned char c) {
return !std::isdigit(c);
}) == s.end();
}
int main(int argc, char **argv) {
if (argc > 2) {
const std::string a = argv[1], b = argv[2];
if (!is_number(a) || !is_number(b)) {
std::cerr << "Use only numbers.\n";
return 1;
}
Math * math = new Math(std::stoi(a), std::stoi(b));
std::cout << math->result() << '\n';
// HERE SHOULD BE delete TO DISLOCATE
} else {
std::cerr << "Use: " << argv[0] << " num1 num2\n";
return 1;
}
return 0;
}
Also, in addition to compiling without flags, you include all possible flags for error detection:
g++ -Wall -Werror -Wextra -Wpedantic main.cpp
But still, no failure was detected.
Then, you can rerun Valgrind:
valgrind -s --leak-check=yes ./a.out 1 2
However, this time the detected error appears as listed in the output below:
==5857== Memcheck, a memory error detector
==5857== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==5857== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==5857== Command: ./a.out 1 2
==5857==
3
==5857==
==5857== HEAP SUMMARY:
==5857== in use at exit: 8 bytes in 1 blocks
==5857== total heap usage: 3 allocs, 2 frees, 73,736 bytes allocated
==5857==
==5857== 8 bytes in 1 blocks are definitely lost in loss record 1 of 1
==5857== at 0x4845013: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==5857== by 0x109377: main (in /home/marcos/a.out)
==5857==
==5857== LEAK SUMMARY:
==5857== definitely lost: 8 bytes in 1 blocks
==5857== indirectly lost: 0 bytes in 0 blocks
==5857== possibly lost: 0 bytes in 0 blocks
==5857== still reachable: 0 bytes in 0 blocks
==5857== suppressed: 0 bytes in 0 blocks
==5857==
==5857== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Note that Memcheck
, a Valgrind tool, detected that you forgot to deallocate the placeholder!
If your Valgrind shows an output error: Fatal error at startup
I recommend you to install the libc6-dbg
library, for example:
sudo apt install libc6-dbg:i386
Defining the
i386
architecture is important!
For more information use help
and the manual:
valgrind --help
man valgrind
memtool cpp clanguage cppdaily
Comments