Memory Debugging Tools
Find memory bugs with professional tools! Learn Valgrind, AddressSanitizer (ASan), and how to detect memory leaks, buffer overflows, and use-after-free errors.
Track Your Progress
Sign in to save your learning progress
What You Will Learn
- ✓Detect memory leaks with Valgrind
- ✓Use AddressSanitizer (-fsanitize=address)
- ✓Find buffer overflows
- ✓Debug use-after-free bugs
- ✓Interpret memory debugging output
?Why Debug Memory Issues?
Memory bugs are the hardest to find in C. They can cause crashes, silent data corruption, or security vulnerabilities. Professional tools catch them automatically!
Memory Leaks
Forgot to free()
Buffer Overflow
Write past array
Use After Free
Dangling pointer
Uninitialized
Random values
Memory Debugging Tools at a Glance
Valgrind
Thorough
~10-50x slower
ASan
Fast & Built-in
~2x slower
MSan
Uninitialized
Clang only
UBSan
Undefined Behavior
Minimal overhead
Use ASan for daily development, Valgrind for thorough testing before release
01Valgrind: The Classic Tool
Valgrind runs your program in a virtual machine, tracking every memory access. It's thorough but slow (~10-50x slower). Great for finding leaks!
Install Valgrind:
# Ubuntu/Debian
sudo apt install valgrind
# macOS (homebrew)
brew install valgrind1// buggy_program.c - Has memory bugs!2#include <stdio.h>3#include <stdlib.h>4#include <string.h>56int main() {7 // Bug 1: Memory leak8 int* leaked = malloc(100 * sizeof(int));9 // Never freed!10 11 // Bug 2: Buffer overflow12 char buffer[10];13 strcpy(buffer, "This is way too long!"); // Overflow!14 15 // Bug 3: Use after free16 int* ptr = malloc(sizeof(int));17 *ptr = 42;18 free(ptr);19 printf("Value: %d\n", *ptr); // Use after free!20 21 // Bug 4: Uninitialized read22 int uninit;23 if (uninit > 0) { // Reading garbage!24 printf("Positive\n");25 }26 27 return 0;28}Run with Valgrind:
# Compile with debug symbols
gcc -g buggy_program.c -o buggy
# Run with Valgrind
valgrind --leak-check=full ./buggyValgrind Output:
==12345== at 0x109189: main (buggy_program.c:17)
==12345== Address 0x4a47040 is 0 bytes inside a block of size 4 free'd
==12345== LEAK SUMMARY:
==12345== definitely lost: 400 bytes in 1 blocks
==12345== indirectly lost: 0 bytes in 0 blocks
02AddressSanitizer (ASan)
ASan is built into GCC and Clang. It's much faster than Valgrind (~2x slower) and catches more types of bugs. Just add a compiler flag!
Compile with ASan:
gcc -fsanitize=address -g program.c -o program1// Run with ASan - Heap buffer overflow2#include <stdio.h>3#include <stdlib.h>45int main() {6 int* arr = malloc(10 * sizeof(int));7 8 // Bug: Writing past the allocated array9 for (int i = 0; i <= 10; i++) { // Should be < 1010 arr[i] = i; // i=10 is out of bounds!11 }12 13 free(arr);14 return 0;15}ASan Output:
==12345==ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 4 at 0x602000000038
#0 0x4011f8 in main heap_overflow.c:9
0x602000000038 is located 0 bytes to the right of 40-byte region
allocated at:
#0 0x7f... in malloc
#1 0x4011b8 in main heap_overflow.c:5
03Other Sanitizers
| Sanitizer | Flag | Detects |
|---|---|---|
| ASan | -fsanitize=address | Buffer overflow, use-after-free, leaks |
| MSan | -fsanitize=memory | Uninitialized reads |
| UBSan | -fsanitize=undefined | Integer overflow, null deref, etc. |
| TSan | -fsanitize=thread | Data races in threaded code |
| LSan | -fsanitize=leak | Memory leaks only (fast) |
1// Compile with multiple sanitizers2// gcc -fsanitize=address,undefined -g program.c34// UBSan example - Integer overflow5#include <stdio.h>6#include <limits.h>78int main() {9 int x = INT_MAX;10 int y = x + 1; // Signed integer overflow - UB!11 printf("y = %d\n", y);12 13 // Shift overflow14 int z = 1 << 32; // UB: shifting by width of type15 16 // Null dereference17 int* ptr = NULL;18 // *ptr = 5; // Would crash with UBSan message19 20 return 0;21}04Common Bugs & Detection
Memory Leak
Bug:
1char* str = malloc(100);2strcpy(str, "Hello");3// Forgot free(str)!4return 0;Fix:
1char* str = malloc(100);2strcpy(str, "Hello");3// Use str...4free(str); // Don't forget!5return 0;Double Free
Bug:
1int* p = malloc(sizeof(int));2free(p);3free(p); // Double free!Fix:
1int* p = malloc(sizeof(int));2free(p);3p = NULL; // Set to NULL4// free(p); // free(NULL) is safeStack Buffer Overflow
Bug:
1char buf[10];2gets(buf); // NEVER use gets!3// User types 50 chars...Fix:
1char buf[10];2fgets(buf, sizeof(buf), stdin);3// Safe: max 9 chars + null05Understanding Valgrind Output
Valgrind output can look intimidating at first, but once you understand the format, it becomes an invaluable debugging tool. Here's how to read and interpret the most common messages.
Invalid Read/Write
This means you accessed memory you shouldn't have — reading past an array, dereferencing a freed pointer, or accessing unallocated memory. The output tells you the size (1, 4, 8 bytes), the exact address, and the line in your code.
==12345== at 0x109189: main (program.c:15)
Definitely Lost
Memory you allocated but never freed AND no longer have any pointer to. This is a confirmed memory leak. The stack trace shows where the memory was allocated, helping you find where to add the missing free() call.
==12345== at 0x4C2BBAF: malloc (vg_replace_malloc.c:299)
==12345== by 0x109145: main (program.c:8)
Still Reachable
Memory that wasn't freed but is still pointed to when the program exits. Often from global variables or intentionally long-lived allocations. Less severe than "definitely lost" but should still be reviewed.
Conditional Jump on Uninitialized Value
You used an uninitialized variable in a condition (if/while/for). The program behavior depends on garbage memory values. Always initialize variables before use.
==12345== at 0x109167: main (program.c:12)
06Best Practices
✓Development
- • Always compile with
-Wall -Wextra - • Use
-fsanitize=address,undefinedin dev - • Run Valgrind before releases
- • Set pointers to NULL after free
CI/CD
- • Run tests with sanitizers enabled
- • Use
ASAN_OPTIONS=detect_leaks=1 - • Fail build on any sanitizer error
- • Track memory usage over time
Recommended Compile Flags:
gcc -Wall -Wextra -g -fsanitize=address,undefined -fno-omit-frame-pointer program.cMemory Debugging Workflow:
1. During Development: Compile with ASan flags. It catches most bugs instantly with minimal slowdown. Make this your default development build.
2. Before Commits: Run your test suite with sanitizers enabled. Any test that triggers a sanitizer error should fail the build.
3. Before Release: Do a full Valgrind run. It's slower but catches subtle leaks that ASan might miss. Use --leak-check=full for detailed reports.
4. In CI Pipeline: Automate sanitizer runs. Set ASAN_OPTIONS=halt_on_error=1to fail fast on any memory error.
!Code Pitfalls: Common Mistakes & What to Watch For
copied C code frequently contains memory bugs (leaks, use-after-free, buffer overflows) that only Valgrind or AddressSanitizer can detect.
copied code often "works" in simple tests but contains hidden memory bugs. For example, it might allocate memory in a loop without freeing it, use freed memory that happens to still contain valid data, or write one byte past an array boundary. These bugs are invisible without specialized tools.
The Trap: Memory bugs in copied code often don't crash immediately — they corrupt data silently or crash intermittently. Copied code can't predict when malloc() will fail, doesn't track memory ownership across function calls, and often forgets to free memory on error paths.
The Reality: Always run copied code through Valgrind or compile with AddressSanitizer (-fsanitize=address). These tools catch bugs that would otherwise take hours to debug. Make memory debugging part of your standard workflow, not an afterthought.
07Frequently Asked Questions
Q:How do I debug memory leaks in long-running programs?
A: For long-running programs like servers, use Valgrind with --leak-check=full --show-reachable=yes. Run your program, exercise key functionality, then gracefully shut it down. Valgrind will report all memory not freed at exit. For continuous monitoring, consider using AddressSanitizer's leak detection or tools like mtrace.
Q:What does "heap-use-after-free" mean?
A: You're accessing memory that was already freed. This happens when you free a pointer but still have other pointers to that memory. The fix: set pointers to NULL after freeing, or redesign your code to have clear ownership of memory. This is one of the most dangerous bugs because it can appear to work until memory is reused.
Q:Should I use Valgrind or AddressSanitizer?
A: Use ASan for daily development — it's faster. Use Valgrind for thorough leak detection before releases. They catch slightly different things, so using both is ideal.
Q:Can I use sanitizers in production?
A: Generally no — they add significant overhead (2-10x slower). Use them in development, testing, and CI pipelines. Production builds should be compiled without sanitizers.
Q:Why does Valgrind say "still reachable"?
A: Memory is "still reachable" if pointers to it exist at program exit — you could have freed it but didn't. It's not as bad as "definitely lost" but still worth fixing.
Q:ASan says "stack-use-after-return" — what does that mean?
A: You returned a pointer to a local variable! When the function returns, that stack memory is gone. Return a malloc'd pointer or pass a buffer as parameter instead.
Q:My program crashes only sometimes — how do I find the bug?
A: Intermittent crashes often indicate memory bugs. Run with Valgrind or ASan — they make undefined behavior more consistent and report the exact location. Memory errors that "work" sometimes are the most dangerous; tools make them fail reliably.
07Summary
Memory Debugging Tools:
- Valgrind — Thorough, finds leaks
- ASan — Fast, built into compiler
- MSan — Uninitialized memory
- UBSan — Undefined behavior
- Compile with
-gfor line numbers - Use sanitizers during development
- Set freed pointers to NULL
- Always check malloc returns
08Prevention Tips
Always Initialize Variables
Declare variables with initial values: int x = 0; not justint x;. This prevents reading uninitialized memory and makes debugging easier.
Set Pointers to NULL After Free
After free(ptr), do ptr = NULL;. This prevents use-after-free bugs—dereferencing NULL crashes immediately, making the bug obvious.
Run Sanitizers in CI
Add ASan and UBSan builds to your continuous integration pipeline. They catch issues that manual testing misses. The small performance overhead during testing is worth the bugs you'll catch early.
Test Your Knowledge
Related Tutorials
Debugging with GDB
Master the GNU Debugger! Learn to install GDB on Windows, Linux, and macOS, set breakpoints, step through code, inspect variables, and find bugs like a professional developer.
Dynamic Memory Allocation
Allocate memory at runtime! Use malloc, calloc, realloc, and free. Create arrays whose size you don't know until the program runs.
Environment Variables in C
Read and modify environment variables in C! Learn getenv(), setenv(), unsetenv(), environ array, and how to configure program behavior via environment.
Have Feedback?
Found something missing or have ideas to improve this tutorial? Let us know on GitHub!