$ clang -g -O0 07_double_free.c -o bin/07_double_free
$ ./bin/07_double_free ; echo "exit=$?"
first free done
free(): double free detected in tcache 2
Aborted (core dumped)
exit=134$ clang -g -O0 07_double_free.c -o bin/07_double_free
$ ./bin/07_double_free ; echo "exit=$?"
first free done
free(): double free detected in tcache 2
Aborted (core dumped)
exit=134$ gdb -q -batch -ex 'set debuginfod enabled off' -ex run -ex bt \
./bin/07_double_free
free(): double free detected in tcache 2
Program received signal SIGABRT, Aborted.
0x00007ffff7e243cc in __pthread_kill_implementation () from /lib64/libc.so.6
#0 0x00007ffff7e243cc in __pthread_kill_implementation () from /lib64/libc.so.6
#1 0x00007ffff7dca15e in raise () from /lib64/libc.so.6
#2 0x00007ffff7db16d0 in abort () from /lib64/libc.so.6
#3 0x00007ffff7db273b in __libc_message_impl.cold () from /lib64/libc.so.6
#4 0x00007ffff7e2e665 in malloc_printerr () from /lib64/libc.so.6
#5 0x00007ffff7e33b18 in tcache_double_free_verify () from /lib64/libc.so.6
#6 0x00000000004004d0 in main () at 07_double_free.c:89
How to read it:
bt bottom-up: frame #6 is YOUR code — main at line 89, the SECOND
free. Frames above are glibc's detector firing: tcache_double_free_verify
-> malloc_printerr -> abort -> raise. gdb hands you the offending free.$ valgrind -q ./bin/07_double_free
==59011== Invalid free() / delete / delete[] / realloc() (heap addr repr.)
==59011== at 0x4843E43: free (vg_replace_malloc.c:990)
==59011== by 0x4004CF: main (07_double_free.c:89)
==59011== Address 0x4a6a040 is 0 bytes inside a block of size 4 free'd
==59011== at 0x4843E43: free (vg_replace_malloc.c:990)
==59011== by 0x4004B5: main (07_double_free.c:87)
==59011== Block was alloc'd at
==59011== by 0x4004A8: main (07_double_free.c:86)
How to read it:
"Invalid free()" at main (...:89) -> the bad (second) free.
"Address ... is ... a block ... free'd at main (...:87)" -> it was
ALREADY freed here. valgrind shows both frees + the original malloc.
(Note: under valgrind the program does NOT abort the same way — valgrind
intercepts free, reports, and lets it continue, so you may see exit 0.)$ clang -g -O0 -fsanitize=address 07_double_free.c -o bin/07_double_free.asan
$ ./bin/07_double_free.asan
==59026==ERROR: AddressSanitizer: attempting double-free on 0x7be25bde0010 in thread T0:
#0 0x4a593a in free
#1 0x4e9e7f in main .../07_double_free.c:89:5
freed by thread T0 here:
#1 0x4e9e65 in main .../07_double_free.c:87:5
previously allocated by thread T0 here:
#1 0x4e9e58 in main .../07_double_free.c:86:14
ASan says it plainly: "attempting double-free", with the second free
(89), the first free (87), and the allocation (86).ROOT CAUSE vs CRASH SITE: The crash is inside glibc's `free`, but your bug is the redundant second `free(p)` at line 89 — the root cause is ownership confusion (two code paths both think they own and must free p).
heap error
2 ------------------------------------------------------------
`free(p)` is called twice on the same pointer. The second free corrupts the allocator's bookkeeping. Buggy line: the SECOND `free(p);`.
Modern glibc DETECTS this and aborts: "free(): double free detected in tcache 2", SIGABRT, exit 134 (128 + 6). Deterministic here (glibc tcache check).
Free exactly once; null the pointer so a second free is a harmless no-op: free(p); p = NULL; // free(NULL) is defined and does nothing
Insert `p = NULL;` between the two frees. The program now runs clean (free(NULL) is a no-op) — exit 0, no abort. ============================================================
#include <stdlib.h>
#include <stdio.h>
int main(void) {
int *p = malloc(sizeof(int)); /* allocate one int */
free(p); /* first free: legitimate */
printf("first free done\n");
free(p); /* <-- THE BUG: second free of same ptr */
return 0;
}A lab is done when you can say what it showed on your machine — not when you have read it.
It decides which problems come first, which drills open, which labs you see and how the Learn surface is organised. Change it any time from the switch in the top bar, or press Escape to look around first.