Dr. Roger Ianjamasimanana

How to use valgrind in C to detect memory issues?

By Dr. Roger Ianjamasimanana

1. What is valgrind?

Valgrind is a powerful debugging and profiling tool for Linux-based systems. It is used to detect memory leaks, invalid memory access, uninitialized memory usage, and other memory-related issues in C, C++, and other languages.

When you run a C program with Valgrind, it monitors all memory allocations (malloc, calloc, realloc, etc.) and deallocations (free) to ensure every block of memory is properly freed. If you forget to free memory, Valgrind will report it as a memory leak.

2. How to install valgrind?

2.1 Linux (Ubuntu/Debian)

To install valgrind on Ubuntu or Debian, run the following commands on your terminal:


# Update package list
sudo apt-get update

# Install Valgrind
sudo apt-get install valgrind

2.2 macOS

Valgrind isn’t officially supported on recent versions of macOS and is not available through the standard Homebrew formula on modern Macs—particularly on Apple Silicon (M1/M2) chips and newer Intel macOS versions.

2.3 Fedora/Red Hat (RHEL/CentOS)

To install valgrind on Fedora/Red Hat, use the followinf commands:


# Update system packages
sudo dnf update

# Install Valgrind
sudo dnf install valgrind

2.4 Windows

Valgrind does not natively support Windows. However, you can use WSL (Windows Subsystem for Linux) to run valgrind on a Windows system. After you have configured WSL, you can use normal Linux command depending on your operating system. For example, if your default linux distribution is ubuntu, you can do the following:


# Open WSL and update the package list
sudo apt-get update

# Install valgrind
sudo apt-get install valgrind

3. How to use valgrind to detect memory leaks

To run your code with valgrind, use this command:


gcc  your_program.c -o your_program
valgrind --leak-check=full --show-leak-kinds=all ./your_program 

3.1 Valgrind output examples

3.1.1 Outputs without memory leaks

Here is an example of outputs from valgrind where there are no memory leaks:


==2657576== Memcheck, a memory error detector
==2657576== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2657576== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2657576== Command: ./no_memory_leak
==2657576== 
Freeing memory.
==2657576== 
==2657576== HEAP SUMMARY:
==2657576==     in use at exit: 0 bytes in 0 blocks
==2657576==   total heap usage: 2 allocs, 2 frees, 1,064 bytes allocated
==2657576== 
==2657576== All heap blocks were freed -- no leaks are possible
==2657576== 
==2657576== For lists of detected and suppressed errors, rerun with: -s
==2657576== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Explanation of the output
✍️
Memcheck
  • The first lines indicate that valgrind ran in Memcheck mode to detect memory errors and track memory usage.
  • Command: ./no_memory_leak shows the program that valgrind analyzed.
  • "Freeing memory." is a message printed by the program, indicating that memory allocated earlier was properly freed before the program ended.
✍️
HEAP SUMMARY
  • in use at exit: 0 bytes in 0 blocks – By the time the program finished, there were no outstanding memory allocations. This means no memory leaks occurred.
  • total heap usage: 2 allocs, 2 frees, 1,064 bytes allocated – The program allocated memory twice and freed it both times, ensuring balanced memory management.
  • All heap blocks were freed -- no leaks are possible: This statement confirms that every piece of allocated memory was properly released before the program terminated.
✍️
ERROR SUMMARY

ERROR SUMMARY: 0 errors from 0 contexts – valgrind found no memory errors or leaks.

3.1.2 Outputs with memory leaks

Example C code with a memory leak
#include <stdio.h>
#include <stdlib.h>
    
int main() {
    int *ptr = (int *)malloc(10 * sizeof(int));  // Allocate memory for 10 integers
    if (ptr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    for (int i = 0; i < 10; i++) {
        ptr[i] = i * 2; // Store some data in the allocated memory
    }

    printf("Memory was allocated, but not freed.\n");
    // Forgot to free(ptr);

    return 0;
} 

Save the above code to a file named memory_leac.c and compile it with debugging symbols (-g flag) so that valgrind can provide better error messages.


gcc -g -o memory_leak memory_leak.c
valgrind --leak-check=full --show-leak-kinds=all ./memory_leak
Here’s what a valgrind output might look like if you forget to free the memory.

==2657000== Memcheck, a memory error detector
==2657000== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==2657000== Using Valgrind-3.18.1 and LibVEX; rerun with -h for copyright info
==2657000== Command: ./memory_leak
==2657000== 
Memory was allocated, but not freed.
==2657000== 
==2657000== HEAP SUMMARY:
==2657000==     in use at exit: 40 bytes in 1 blocks
==2657000==   total heap usage: 2 allocs, 1 frees, 1,064 bytes allocated
==2657000== 
==2657000== 40 bytes in 1 blocks are definitely lost in loss record 1 of 1
==2657000==    at 0x4848899: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==2657000==    by 0x10919E: main (memory_leak.c:5)
==2657000== 
==2657000== LEAK SUMMARY:
==2657000==    definitely lost: 40 bytes in 1 blocks
==2657000==    indirectly lost: 0 bytes in 0 blocks
==2657000==      possibly lost: 0 bytes in 0 blocks
==2657000==    still reachable: 0 bytes in 0 blocks
==2657000==         suppressed: 0 bytes in 0 blocks
==2657000== 
==2657000== For lists of detected and suppressed errors, rerun with: -s
==2657000== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
Explanation of the output
✍️
Memcheck
  • The first lines (==2657000== Memcheck, a memory error detector) indicate that valgrind is running in Memcheck mode to detect memory leaks and memory errors.
  • Command: ./memory_leak shows the program that valgrind analyzed.
  • The message "Memory was allocated, but not freed." means that our program allocated some memory during its run but never released it back to the system.
✍️
HEAP SUMMARY
  • in use at exit: 40 bytes in 1 blocks – When the program finished, there were still 40 bytes of memory allocated that were never freed. This is a memory leak.
  • total heap usage: 2 allocs, 1 frees, 1,064 bytes allocated – Across the entire run of the program, there were two memory allocations and only one free operation. This imbalance usually leads to a leak.
✍️
LEAK SUMMARY
  • definitely lost: 40 bytes in 1 blocks – This is memory that valgrind is certain was never freed. It was allocated and no pointer to it remains at program exit. It’s a guaranteed memory leak.
  • indirectly lost: (0 bytes) – None in this case. Indirectly lost memory would be memory only reachable by pointers to other leaked memory. Not applicable here.
  • possibly lost: (0 bytes) – None here. Possibly lost memory might be memory still referenced by pointers that valgrind cannot fully track. Not applicable in this case.
  • still reachable: (0 bytes) – None here. Still reachable means memory that wasn't freed but still had pointers pointing to it at exit, which may be okay in some programs. Not applicable here.
✍️
ERROR SUMMARY

The final part ==2657000== ERROR SUMMARY: 1 errors from 1 contexts indicates valgrind found one distinct memory error (the leak) in one location.

What does this mean?

  • We allocated memory at memory_leak.c:5 (as indicated by the stack trace in valgrind’s output) and never freed it.
  • To fix this, we need to ensure that we call free() (see the next section for more details) on every allocated block of memory once we’re done using it.
🛠️ How to fix the memory leaks? The problem with our C program was that we forgot to free the memory allocated with malloc. To fix it, we should use free(ptr) at the end of the program. Here is the corrected code:
#include <stdio.h>
#include <stdlib.h>
    
int main() {
    int *ptr = (int *)malloc(10 * sizeof(int));  // Allocate memory for 10 integers
    if (ptr == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }

    for (int i = 0; i < 10; i++) {
        ptr[i] = i * 2; // Store some data in the allocated memory
    }

    printf("Freeing memory.\n");
    free(ptr); // Free the memory

    return 0;
}

3.2 How to write a leak-free code?

Memory leaks can slow down your application, cause crashes, and waste system resources. Follow these best practices to avoid common memory management issues and write more efficient, leak-free code.

3.2.1 Always free allocated memory

Every time you use malloc(), calloc(), or realloc(), you must also call free() to release the memory when it's no longer needed.


int *ptr = (int *) malloc(sizeof(int) * 10);
// Do some work with ptr
free(ptr); // important

3.2.2 Avoid double free errors

Calling free() on the same pointer more than once can cause program crashes. To prevent this, set the pointer to NULL after freeing it.

// Bad: Double-freeing memory
int *ptr = (int *) malloc(sizeof(int) * 10);
free(ptr);
free(ptr); // This will cause a crash

solution: set the pointer to NULL after freeing it.

// Set the pointer to NULL after freeing it.
int *ptr = (int *) malloc(sizeof(int) * 10);
free(ptr);
ptr = NULL; // No accidental double freeing of memory

3.2.3 Check for memory allocation failures

Sometimes malloc() or calloc() may fail to allocate memory. Always check if the pointer is NULL before using it.

// Good: Check for NULL after malloc
int *ptr = (int *) malloc(sizeof(int) * 10);
if (ptr == NULL) {
printf("Memory allocation failed!\n");
exit(1);
}

3.2.4 Avoid memory leaks in loops

When dynamically allocating memory inside a loop, always ensure the memory is freed at the end of each iteration to avoid leaks.

// Bad: memory leak in loop
for (int i = 0; i < 100; i++) {
int *ptr = (int *) malloc(sizeof(int) * 10);
// Do something with ptr, but forget to free it
}

Solution: free the memory inside each iteration of the loop.

// Good: free the memory at the end of each loop
for (int i = 0; i < 100; i++) {
int *ptr = (int *) malloc(sizeof(int) * 10);
// Do something with ptr
free(ptr);
}

3.2.5 Use smart pointers (C++)

In C++, avoid manual memory management by using smart pointers like std::unique_ptr or std::shared_ptr. These automatically release memory when it is no longer used.


#include <memory>
void example() {
std::unique_ptr<int> ptr = std::make_unique<int>(10);
// No need to free memory, it will be automatically released
}

feature-top
Readers’ comment
feature-top
Log in to add a comment
🔐 Access