Appearance
[中文版本](/posts/c教程/zh-CN/08-调试测试与 profiling/072-Using Valgrind for Memory Checking)
If gdb helps you see how your program runs, Valgrind helps you see where it leaks. C gives you raw control over memory, and that means you’re responsible for every allocation, deallocation, and pointer access.
Valgrind is your best friend when you need to find:
- Memory leaks (forgotten
free()) - Invalid reads/writes
- Double frees
- Use-after-free errors
Let’s learn how to use it to make your programs solid and leak-free.
Step 1. Installing Valgrind
On Linux:
sudo apt install valgrindOn macOS (with Homebrew):
brew install valgrindCompile your program with debug symbols:
gcc -g memory.c -o memoryStep 2. Running with Valgrind
Run your program under Valgrind:
valgrind ./memoryValgrind runs your program inside a virtual CPU and monitors every memory operation. At the end, it prints a detailed report of allocations and leaks.
Step 3. A Simple Example
Here’s a program with two common mistakes: a leak and an invalid free.
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(10 * sizeof(int));
p[10] = 42; // invalid write (out of bounds)
return 0; // forgot to free(p)
}Run it:
gcc -g mem_bug.c -o mem_bug
valgrind ./mem_bugOutput:
==1234== Invalid write of size 4
==1234== at 0x1091A: main (mem_bug.c:6)
==1234== Address 0x5201048 is 0 bytes after a block of size 40 alloc'd
==1234== at 0x484186F: malloc (vg_replace_malloc.c:380)
==1234==
==1234== HEAP SUMMARY:
==1234== definitely lost: 40 bytes in 1 blocks
==1234== LEAK SUMMARY:
==1234== definitely lost: 40 bytes in 1 blocksValgrind caught both the invalid access and the leak.
Step 4. Fixing the Errors
Correct version:
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(10 * sizeof(int));
if (!p) return 1;
p[9] = 42; // valid index
free(p);
}Run again:
valgrind ./mem_bugOutput:
== All heap blocks were freed -- no leaks are possible
== ERROR SUMMARY: 0 errors from 0 contextsClean and perfect.
Step 5. Detecting Use-After-Free
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(sizeof(int));
*p = 5;
free(p);
printf("%d\n", *p); // using freed memory
}Valgrind says:
==1234== Invalid read of size 4
==1234== at 0x1091A: main (use_after_free.c:7)
==1234== Address 0x5201040 is 0 bytes inside a block of size 4 free'dIt even shows where the block was freed.
Step 6. Detecting Double Free
#include <stdlib.h>
int main(void) {
int *p = malloc(4);
free(p);
free(p);
}Valgrind output:
==1234== Invalid free() / delete / delete[]
==1234== at 0x4845DEF: free (vg_replace_malloc.c:872)
==1234== Address 0x5201040 was freed previouslyStep 7. Memory Leak Categories
Valgrind divides leaks into categories:
| Type | Meaning |
|---|---|
| definitely lost | No pointer to the block remains, true leak |
| indirectly lost | Referenced by a leaked block |
| possibly lost | Pointer may exist but Valgrind can’t confirm |
| still reachable | Program ended but memory wasn’t freed (often harmless) |
Step 8. Getting a Clean Report
To check for true leaks only:
valgrind --leak-check=full --show-leak-kinds=definite ./programFor more verbose tracking:
valgrind --track-origins=yes ./programThat flag tells you where an uninitialized variable first appeared.
Step 9. Checking for Stack or Uninitialized Values
#include <stdio.h>
int main(void) {
int x;
printf("%d\n", x); // uninitialized read
}Valgrind output:
==1234== Use of uninitialised value of size 4
==1234== at 0x1091A: main (uninit.c:5)Always initialize your variables!
Step 10. Tiny Code: Leak Detector
#include <stdlib.h>
void leak1(void) { malloc(100); }
void leak2(void) { char *p = malloc(50); free(p); }
int main(void) {
leak1();
leak2();
}Run:
valgrind --leak-check=full ./leaksOutput:
==1234== 100 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1234== LEAK SUMMARY:
==1234== definitely lost: 100 bytes in 1 blocksOnlyleak1() forgot tofree(), Valgrind pinpointed it exactly.
Why It Matters
Valgrind helps you:
- Find hidden memory leaks
- Detect invalid pointer usage
- Catch uninitialized values
- Write safer, cleaner, more reliable C
It’s an essential tool in your workflow, especially for long-running programs, servers, or systems software.
Try It Yourself
Write a program that allocates multiple blocks and forgets to free one.
Intentionally usep[10] on amalloc(10) block.
Trigger a use-after-free and find it in Valgrind.
Use--track-origins=yes to trace uninitialized data.
Refactor your code until Valgrind reports:
All heap blocks were freed -- no leaks are possibleNext, you’ll explore Assertions and Defensive Programming, techniques to catch logic errors before they reach runtime crashes.
