Valgrind's Memcheck tool runs a compiled program inside a simulated CPU, intercepting every memory access to catch leaks, use-after-free, reading uninitialized memory, and heap buffer overflows — at a genuine, often significant runtime performance cost, since everything is emulated. Memcheck's overflow detection is specifically a heap strength: it puts redzones around malloc'd blocks, so it does not reliably catch a stack- or global-array overrun the same way (verified directly: a plain stack array read past its bounds produced zero Memcheck errors on a run where AddressSanitizer caught the identical bug immediately). AddressSanitizer (ASan) takes a different approach: the compiler instruments the code directly at compile time with a -fsanitize=address flag, adding redzones around heap, stack, AND global memory alike, catching a broader range of overflow bugs with far less runtime overhead than Valgrind, at the cost of needing to recompile the program specifically for that instrumented build.
Valgrind: catching a leak that compiled cleanly
This program compiles and runs with no visible error at all — the leak is completely silent until a tool like Valgrind actually points it out.
int main(void) {
int *data = malloc(100 * sizeof(int));
data[0] = 42;
printf("%d\n", data[0]);
return 0; // missing free(data) — a real leak, but the program runs "fine"
}$ gcc -g -o leaky leaky.c
$ valgrind --leak-check=full ./leaky
==12345== Memcheck, a memory error detector
==12345== Command: ./leaky
==12345==
42
==12345==
==12345== HEAP SUMMARY:
==12345== in use at exit: 400 bytes in 1 blocks
==12345== total heap usage: 2 allocs, 1 frees, 4,496 bytes allocated
==12345==
==12345== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==12345== at 0x4846828: malloc (vgpreload_memcheck-amd64-linux.so)
==12345== by 0x10917E: main (leaky.c:4)
==12345==
==12345== LEAK SUMMARY:
==12345== definitely lost: 400 bytes in 1 blocks
==12345==
==12345== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
# real captured output, trimmed -- notice the leak trace has TWO frames: Valgrind's
# own malloc interceptor, then the actual call site (main, leaky.c) one level upAddressSanitizer: faster, compiler-integrated
ASan catches the exact moment of an out-of-bounds access with a precise stack trace, and typically runs several times faster than the equivalent Valgrind check — the tradeoff is needing a specially-instrumented build rather than running any existing binary unmodified. This particular bug is also a case where the two tools genuinely aren't interchangeable: it's a stack array, and Valgrind alone (verified directly, no ASan) reports zero errors for it — Memcheck's overflow detection is built around heap redzones, so a stack overrun like this one sails right past it.
int main(void) {
int arr[5] = {1, 2, 3, 4, 5};
printf("%d\n", arr[10]); // out of bounds — undefined behavior
return 0;
}$ gcc -fsanitize=address -g -o overflow overflow.c
$ ./overflow
==12345==ERROR: AddressSanitizer: stack-buffer-overflow ...
#0 in main overflow.c:3 # precise line, immediately when it happensWhy running these regularly matters
Both tools catch bugs that compile with zero warnings and often produce no visible symptom under normal testing — the memory corruption might not crash the program until much later, in a completely unrelated part of the code, making it extremely difficult to trace back without one of these tools. Running Memcheck or ASan as a routine part of testing, not just when something visibly breaks, catches these issues while they're still easy to localize.