Categories: TutorialsUbuntu

Detecting and Fixing Memory Leaks with Valgrind

Memory leaks are among the most frustrating bugs to track down in C and C++ applications. They silently consume resources, degrade performance, and can eventually cause your application to crash.

Fortunately, Valgrind offers a powerful suite of tools to help identify and fix these elusive issues. In this guide, we’ll explore how to effectively use Valgrind to detect and resolve memory leaks in your applications.

What is Valgrind?

Valgrind is an instrumentation framework used to build dynamic analysis tools that detect memory management and threading bugs. Its most popular tool, Memcheck,

Sponsored
identifies memory-related errors such as:

  • Memory leaks (allocated memory that’s never freed)
  • Use of uninitialized memory
  • Reading/writing memory after it has been freed
  • Memory buffer overflows
  • Incorrect usage of memory allocation functions

Originally developed for Linux, Valgrind now supports multiple platforms including macOS and FreeBSD.

Read: How to fix high memory usage in Ubuntu

Getting Started with Valgrind

Installation

Before detecting memory leaks, ensure Valgrind is installed on your system:

# For Debian-based systems (Ubuntu)
sudo apt install valgrind

# For Red Hat-based systems (CentOS, Fedora)
sudo yum install valgrind

# For Arch-based systems
sudo pacman -Syu valgrind

# For FreeBSD
sudo pkg ins valgrind
    

Basic Usage

The simplest way to use Valgrind is by running your executable through it:

valgrind ./your_program arg1 arg2

This command runs your program with Memcheck enabled and reports any issues found.

Effective Memory Leak Detection

For thorough memory leak detection, use:

valgrind --leak-check=full 
         --show-leak-kinds=all 
         --track-origins=yes 
         --verbose 
         --log-file=valgrind-out.txt 
         ./your_program arg1 arg2
    

This command provides detailed leak reports, tracks the origins of uninitialized values, and logs the output for review.

Sponsored

Read: Monitor and Optimize Memory Usage on Ubuntu 22.04 for Peak Performance

Understanding Valgrind’s Output

After analysis, Valgrind outputs a detailed report. For example:

HEAP SUMMARY:
    in use at exit: 0 bytes in 0 blocks
  total heap usage: 636 allocs, 636 frees, 25,393 bytes allocated
 
All heap blocks were freed -- no leaks are possible
 
ERROR SUMMARY: 0 errors from 0 contexts
    

This indicates that all allocated memory was properly freed. If leaks are detected, Valgrind will list details for each lost memory block.

Debugging Memory Leaks

Compiling with Debug Information

To get detailed, line-by-line information about memory issues, compile your program with debug flags:

# Standard compilation
gcc -o executable -std=c11 -Wall main.c

# With debug information
gcc -o executable -std=c11 -Wall -ggdb3 main.c
    

This enables Valgrind to report the exact source file and line number where the leak occurred. For optimizations that preserve debuggability, use -Og instead.

Common Memory Leak Scenarios and Solutions

Scenario 1: Forgetting to Free Allocated Memory

Example:

#include 

int main() {
    char* string = malloc(5 * sizeof(char)); // LEAK: not freed!
    return 0;
}
    

Solution: Always free dynamically allocated memory when done.

#include 

int main() {
    char* string = malloc(5 * sizeof(char));
    // Use string...
    free(string); // Memory freed
    return 0;
}
    

Scenario 2: Losing Track of Pointer References

Example:

#include 
#include 

struct _List {
    int32_t* data;
    int32_t length;
};
typedef struct _List List;

List* resizeArray(List* array) {
    int32_t* dPtr = array->data;
    dPtr = realloc(dPtr, 15 * sizeof(int32_t)); // Leak: original pointer not updated
    return array;
}

int main() {
    List* array = calloc(1, sizeof(List));
    array->data = calloc(10, sizeof(int32_t));
    array = resizeArray(array);
    
    free(array->data);
    free(array);
    return 0;
}
    

Solution: Update the original pointer when using realloc:

List* resizeArray(List* array) {
    array->data = realloc(array->data, 15 * sizeof(int32_t)); // Fixed
    return array;
}
    

Scenario 3: Memory Access Errors

Example:

#include 
#include 

int main() {
    char* alphabet = calloc(26, sizeof(char));
    for(uint8_t i = 0; i 
Ubuntu Server Admin

Recent Posts

Kolla Ansible OpenStack Installation (Ubuntu 24.04)

Kolla Ansible provides production-ready containers (here, Docker) and deployment tools for operating OpenStack clouds. This…

14 hours ago

Canonical announces first Ubuntu Desktop image for Qualcomm Dragonwing™ Platform with Ubuntu 24.04

This public beta enables the full Ubuntu Desktop experience on the Qualcomm Dragonwing™ QCS6490 and…

2 days ago

The long march towards delivering CRA compliance

Time is running out to be in full compliance with the EU Cyber Resilience Act,…

2 days ago

Extra Factor Authentication: how to create zero trust IAM with third-party IdPs

Identity management is vitally important in cybersecurity. Every time someone tries to access your networks,…

3 days ago

Ubuntu Weekly Newsletter Issue 889

Welcome to the Ubuntu Weekly Newsletter, Issue 889 for the week of April 20 –…

4 days ago

From pidfd to Shimanami Kaido: My RubyKaigi 2025 Experience

Introduction I just returned from RubyKaigi 2025, which ran from April 16th to 18th at…

5 days ago