Question
How can I use Valgrind to find memory leaks in a C program?
I am using Ubuntu 10.04, and I have a source file named a.c. I want to check whether my program leaks memory and understand how to run Valgrind correctly to identify the problem areas.
Short Answer
By the end of this page, you will know how to compile a C program for debugging, run it with Valgrind, read the leak report, and understand the most common causes of memory leaks in C programs.
Concept
In C, when you allocate memory dynamically using functions such as malloc(), calloc(), or realloc(), you are responsible for freeing that memory with free() when you no longer need it.
A memory leak happens when your program allocates memory but loses the ability to release it later. This matters because:
- the program wastes memory
- long-running programs can grow in memory usage over time
- leaks make software harder to maintain and debug
- repeated leaks can eventually slow down or crash systems
Valgrind is a debugging tool that runs your program inside an analysis environment. One of its most common tools, Memcheck, tracks memory usage and reports problems such as:
- memory leaks
- invalid reads and writes
- use of uninitialized values
- freeing memory incorrectly
- double frees
Valgrind is especially useful in C because the language gives you direct control over memory, but that also means memory bugs are easy to introduce.
To get useful Valgrind output, you should compile your program with debug information using -g. This helps Valgrind show file names and line numbers.
Mental Model
Think of dynamic memory like renting storage boxes.
malloc()means you rent a box.- Using the pointer means you keep the key to that box.
free()means you return the box.- A memory leak happens when you lose the key before returning the box.
Valgrind acts like an auditor watching every box you rent and return. At the end, it tells you:
- which boxes were never returned
- where you rented them
- whether you tried to use a box incorrectly
This makes it much easier to trace memory problems back to the exact place in your code.
Syntax and Examples
To use Valgrind with a C program, the usual workflow is:
- Compile with debug symbols.
- Run the program through Valgrind.
- Read the output.
Install Valgrind
sudo apt-get install valgrind
Compile your program
gcc -g -o a a.c
-gadds debug information-o acreates an executable nameda
Run with Valgrind
valgrind --leak-check=full ./a
Example program with a leak
#include <stdlib.h>
int main(void) {
int *numbers = malloc(10 * sizeof(int));
return 0;
}
Compile and run
Step by Step Execution
Consider this small program:
#include <stdlib.h>
int main(void) {
char *buffer = malloc(20);
buffer[0] = 'A';
buffer[1] = '\0';
return 0;
}
Step-by-step
malloc(20)asks the system for 20 bytes of memory.- The returned address is stored in
buffer. - The program writes
'A'into the first byte. - The program writes the string terminator
\0into the second byte. - The program ends without calling
free(buffer).
What Valgrind sees
- memory was allocated
- the pointer existed
- the memory was never freed
Typical command
gcc -g -o a a.c
valgrind --leak-check=full ./a
Typical output shape
Real World Use Cases
Memory leak detection matters in many real programs:
- Command-line tools: a utility may run briefly, but leaks still indicate poor memory management.
- Servers and daemons: even small leaks become serious if the process runs for days or weeks.
- Embedded or low-memory systems: leaked memory can quickly exhaust limited RAM.
- Data processing programs: repeated allocations inside loops can silently leak large amounts of memory.
- Libraries: if a library leaks memory, every application using it may suffer.
Example scenarios:
- parsing a file and forgetting to free a buffer
- building a linked list and never releasing its nodes
- returning early from a function before cleanup happens
- overwriting a pointer returned by
malloc()and losing the original address
Real Codebase Usage
In real C codebases, developers often use Valgrind together with coding patterns that reduce leak risk.
Common patterns
-
Pair every allocation with a clear owner
- If one function allocates memory, be clear about which function frees it.
-
Use guard clauses carefully
- Early returns are fine, but make sure already-allocated resources are cleaned up first.
-
Centralize cleanup
- Many C programs use a cleanup section near the end of a function.
#include <stdlib.h>
int process(void) {
char *a = malloc(100);
char *b = malloc(200);
if (a == NULL || b == NULL) {
free(a);
free(b);
return 1;
}
free(a);
free(b);
return 0;
}
-
Check pointers after allocation
Common Mistakes
Here are common beginner mistakes when using dynamic memory and Valgrind.
1. Forgetting to compile with -g
Without debug symbols, Valgrind output is harder to read.
gcc -o a a.c
Better:
gcc -g -o a a.c
2. Allocating memory and never freeing it
Broken code:
char *name = malloc(50);
name[0] = 'J';
name[1] = '\0';
Fix:
char *name = malloc(50);
if (name != NULL) {
name[0] = 'J';
name[1] = '\0';
free(name);
}
3. Losing the original pointer
Broken code:
int *p = malloc(10 * ());
p = ;
Comparisons
| Tool or concept | What it does | Best use |
|---|---|---|
| Valgrind Memcheck | Detects leaks and memory misuse while running the program | Finding leaks, invalid reads/writes, double frees |
| Compiler warnings | Reports suspicious code at compile time | Catching obvious mistakes early |
| Manual code review | You inspect allocation and cleanup logic yourself | Understanding ownership and cleanup flow |
| AddressSanitizer | Detects many memory errors with compiler instrumentation | Fast runtime checks during development |
Valgrind vs just reading the code
- Reading code helps you understand ownership.
- Valgrind shows what actually happened at runtime.
- In practice, developers use both.
Leak categories you may see in Valgrind
| Leak kind | Meaning |
|---|---|
| definitely lost |
Cheat Sheet
Basic workflow
gcc -g -o a a.c
valgrind --leak-check=full ./a
Install
sudo apt-get install valgrind
Useful options
valgrind --leak-check=full --show-leak-kinds=all ./a
valgrind --track-origins=yes ./a
What to look for
definitely lost= confirmed leakindirectly lost= leaked through another leaked blockstill reachable= not freed, but still referenced at exit- file and line numbers point to where allocation happened
Good habits
- compile with
-g - check every
malloc()result - free every successful allocation
- avoid overwriting allocated pointers before freeing them
- test small programs first, then larger ones
Common allocation functions
malloc(size)
calloc(count, size)
realloc(ptr, new_size)
free(ptr)
FAQ
How do I run Valgrind on a C program?
Compile the program with debug symbols using gcc -g -o a a.c, then run valgrind --leak-check=full ./a.
Why should I compile with -g before using Valgrind?
The -g flag adds debug information so Valgrind can show source file names and line numbers in its output.
What does definitely lost mean in Valgrind?
It means memory was allocated and Valgrind can no longer find any valid pointer to it. This is a real leak.
Can Valgrind find errors other than memory leaks?
Yes. It can also detect invalid memory access, use of uninitialized values, incorrect frees, and double frees.
Does memory still count as leaked if the program exits?
Yes. Even if the operating system reclaims memory after exit, failing to free memory is still considered a bug.
Why does Valgrind feel slow?
Valgrind instruments your program while it runs, so it is much slower than normal execution. That is expected during debugging.
What if Valgrind output does not show line numbers?
You probably did not compile with debug symbols, or the binary was optimized in a way that reduced useful debug information.
Is Valgrind only for large programs?
No. It is very useful for small programs too, especially when learning memory management in C.
Mini Project
Description
Create a small C program that allocates memory for a dynamic array, uses it, and then frees it correctly. Then run the program with Valgrind to confirm that no memory is leaked. This demonstrates the complete workflow: writing code, compiling with debug symbols, running Valgrind, and interpreting the result.
Goal
Build and test a C program that uses dynamic memory safely and produces a clean Valgrind report.
Requirements
- Write a C program that allocates memory for an integer array using
malloc(). - Store some values in the array and print them.
- Free the allocated memory before the program exits.
- Compile the program with debug symbols.
- Run the program with Valgrind and confirm there are no leaks.
Keep learning
Related questions
Array-to-Pointer Conversion in C and C++ Explained
Learn what array-to-pointer conversion means in C and C++, how array decay works, and how it differs from a pointer to an array.
Building More Fault-Tolerant Embedded C++ Applications for Radiation-Prone ARM Systems
Learn practical C++ and compile-time techniques to reduce soft-error damage in embedded ARM systems exposed to radiation.
C Pointer to Array vs Array of Pointers: How to Read Complex Declarations
Learn the difference between pointer-to-array and array-of-pointers in C, plus a simple rule for reading complex declarations correctly.