C’s most expensive bugs are memory errors: they compile silently, often seem to work, and later crash or become security holes. Sanitizers (-fsanitize=address,undefined) and Valgrind find them.
| Bug | What it is | Caught by |
|---|---|---|
| Buffer overflow | Read/write past the end (or before the start) of a buffer | ASan, -D_FORTIFY_SOURCE |
| Use after free | Access through a pointer to freed memory | ASan, Valgrind |
| Double free | free the same block twice | ASan, glibc checks |
| Memory leak | Lose the last pointer to a block without freeing it | LeakSanitizer, Valgrind |
| Dangling stack pointer | Use a pointer to a local after its function returned | Compiler warning, ASan (use-after-return) |
| Uninitialised read | Read a variable / malloc’d memory before writing it | -Wuninitialized, MSan, Valgrind |
| Invalid free | free a pointer not from malloc (stack array, p + 1) | ASan |
| NULL dereference | Use a pointer that is NULL (e.g. unchecked malloc) | Crash (SIGSEGV), UBSan |
Heap buffer overflow
#include <stdlib.h>
#include <string.h>
int main(void) {
char *name = malloc(5);
strcpy(name, "Alice"); /* needs 6 bytes: 5 letters + '\0' */
free(name);
return 0;
}==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000955
WRITE of size 6 at 0x602000000955 thread T0
0x602000000955 is located 0 bytes after 5-byte region [0x602000000950,0x602000000955)Use after free
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int *p = malloc(sizeof *p);
if (!p) return 1;
*p = 42;
free(p);
printf("%d\n", *p); /* the block now belongs to the allocator */
return 0;
}==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000950
READ of size 4 at 0x602000000950 thread T0
freed by thread T0 here: ... in free
previously allocated by thread T0 here: ... in mallocDouble free
#include <stdlib.h>
int main(void) {
char *buf = malloc(16);
free(buf);
free(buf); /* second free of the same block */
return 0;
}==ERROR: AddressSanitizer: attempting double-free on 0x602000000950 in thread T0Invalid free
#include <stdlib.h>
int main(void) {
int *a = malloc(4 * sizeof *a);
if (!a) return 1;
a++; /* lost the original pointer */
free(a); /* not the address malloc returned */
return 0;
}Stack buffer overflow & use after scope
#include <stdio.h>
int main(void) {
int *p;
{
int inner = 7;
p = &inner;
} /* inner's lifetime ends here */
printf("%d\n", *p);
return 0;
}Memory leak
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char *greet(const char *name) {
char *s = malloc(strlen(name) + 7);
if (s) { strcpy(s, "Hello "); strcat(s, name); }
return s; /* the caller now owns s */
}
int main(void) {
for (int i = 0; i < 3; i++) {
char *msg = greet("Bob");
if (msg) puts(msg);
/* missing free(msg): each iteration leaks 10 bytes */
}
return 0;
}Hello Bob
Hello Bob
Hello Bob==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 30 byte(s) in 3 object(s) allocated from:
#0 in malloc
#1 in greet main.c:6Uninitialised reads
#include <stdio.h>
int main(void) {
int total;
for (int i = 0; i < 3; i++) total += i; /* total starts as garbage */
printf("%d\n", total);
return 0;
}Defensive habits
Initialise every variable at declaration;
= {0}for arrays and structs.Check every
malloc/fopen/reallocresult.Set pointers to
NULLafterfree; keep one clear owner per allocation.Pass buffer sizes everywhere; prefer
snprintf,fgets,strncat/memcpywith explicit lengths.Build debug and test runs with
-g -fsanitize=address,undefined; run Valgrind in CI.Use the
goto cleanuppattern so every exit path frees what was allocated.
What is a dangling pointer and how do you avoid it?
What is the difference between a memory leak and a dangling pointer?
How does AddressSanitizer work?