free(): double free detected in tcache 2 in C
double free detected in tcache 2, free(): invalid pointer, munmap_chunk(): invalid pointer: where these glibc messages come from and how to fix the bad free.
The program runs, prints its result, then dies on a cryptic line that mentions tcache, munmap_chunk or invalid pointer, followed by Aborted. These messages do not come from your code: they come from the C library's allocator, which noticed that it was being asked to free something impossible.
In short
glibc (the C library on Linux) prints these messages when a free is invalid: freeing the same block twice, freeing a shifted pointer, or freeing an address that does not come from the heap. The program stops through abort, and echo $? in the shell prints 134. glibc detects the error after the fact, not the exact line: to find it, run your program under Valgrind, which points at the faulty free with Invalid free() / delete / delete[] / realloc(). The fix is almost always to call free only once, on the exact address returned by malloc, calloc or realloc; when the message mentions corruption, look for an out-of-bounds write before the free.
Where these messages come from
glibc's memory allocator prints them, from its malloc.c file. On every free, the library runs a few consistency checks on its own data structures. When a check fails, it calls an internal function that prints the message and stops the process. There is no way back: the program receives SIGABRT.
The word tcache comes up a lot. It is glibc's "per-thread cache", a per-thread cache of recently freed small blocks, added in glibc 2.26 in 2017 to speed up common allocations. The "2" in tcache 2 is part of the message's fixed text in the glibc source: it depends neither on the block size nor on the number of free calls, and it is neither an index nor an error code.
For all of these messages, echo $? in the shell shows the same value: 134, that is 128 + 6, and 6 is the number of SIGABRT. That is the signature of an abort, not to be confused with the 139 of a segmentation fault.
free(): double free detected in tcache 2
The most common case: the same block freed twice.
#include <stdio.h>#include <stdlib.h>#include <string.h>
int main(void){ char *name = malloc(16); if (name == NULL) return 1; strcpy(name, "grademe"); printf("%s\n", name); free(name); free(name); return 0;}$ gcc -Wall -Wextra -g double.c -o doubledouble.c: In function 'main':double.c:13:5: warning: pointer 'name' used after 'free' [-Wuse-after-free] 13 | free(name); | ^~~~~~~~~~double.c:12:5: note: call to 'free' here 12 | free(name); | ^~~~~~~~~~$ ./doublegrademefree(): double free detected in tcache 2Aborted$ echo $?134Here GCC caught the problem at compile time (-Wuse-after-free), because both free calls sit in the same function. Once the frees are scattered across the code, the compiler sees nothing, and the message only appears at run time. The habit that prevents it: free(name); name = NULL;. A second free(NULL) does nothing.
free(): invalid pointer
This message appears when you pass free an address that is not the start of a block. The classic: a pointer you moved forward, for example to skip spaces, and then freed as is.
#include <stdio.h>#include <stdlib.h>#include <string.h>
int main(void){ char *line = malloc(32); if (line == NULL) return 1; strcpy(line, " hello"); while (*line == ' ') line++; printf("%s\n", line); free(line); return 0;}$ gcc -Wall -Wextra -g offset.c -o offset$ ./offsethellofree(): invalid pointerAbortedline no longer points to the start of the block: it moved two bytes forward. glibc does not recognize that address as the start of a block and refuses it. The fix is to keep the original pointer for the free and walk the string with a copy:
char *line = malloc(32);/* ... */while (*line == ' ') line++;free(line); /* shifted address */char *line = malloc(32);char *p = line;/* ... */while (*p == ' ') p++;free(line); /* start of the block */munmap_chunk(): invalid pointer
The same bug (a shifted pointer) on a large block gives a different message. glibc serves large allocations directly with mmap and frees them with munmap. When the shifted address falls inside one of those blocks, munmap_chunk is the one that complains:
$ ./biga long filemunmap_chunk(): invalid pointerAbortedThe program is offset.c with malloc(200000) instead of malloc(32) and a different string. The diagnosis and the fix are identical: only free the original address.
double free or corruption
A double free on a block too big for the tcache (more than 1,032 bytes requested on 64-bit) but below the mmap threshold, or on a block whose metadata has been overwritten, gives this family of messages ((!prev), (top), (out), (fasttop)):
$ ./double-bigdouble free or corruption (!prev)AbortedThe suffix in parentheses says which internal check failed. For you, the diagnosis stays the same: a block freed twice, or an overflow that corrupted the bookkeeping bytes glibc keeps around each block. In that second case, the real bug is the out-of-bounds write that came earlier, not the free.
Freeing what does not come from the heap
free only accepts addresses returned by malloc, calloc or realloc. Passing it a stack variable or a string literal is undefined behavior. GCC often spots it at compile time. At run time, everything depends on the bytes just before the address: on a stack variable, glibc answers with free(): invalid size or munmap_chunk(): invalid pointer, or the program simply segfaults, sometimes differently from one run to the next. On a string literal, stored in read-only memory, it ends in a segfault:
$ gcc -Wall -Wextra -g literal.c -o literalliteral.c: In function 'main':literal.c:7:5: warning: 'free' called on a pointer to an unallocated object '"grademe"' [-Wfree-nonheap-object] 7 | free(name); | ^~~~~~~~~~$ ./literalSegmentation faultThe rule behind all of these cases is in the free(3) entry: a heap address, exactly as malloc returned it, freed once.
Finding the line with Valgrind
The shared weakness of these messages is that they arrive after the fact: glibc detects the inconsistency, not the line that caused it. Valgrind, on the other hand, intercepts every free and names the file and the line. On the double free:
$ valgrind -q ./doublegrademe==8== Invalid free() / delete / delete[] / realloc()==8== at 0x48884F8: free (vg_replace_malloc.c:989)==8== by 0x40071B: main (double.c:13)==8== Address 0x4a7f040 is 0 bytes inside a block of size 16 free'd==8== at 0x48884F8: free (vg_replace_malloc.c:989)==8== by 0x400713: main (double.c:12)==8== Block was alloc'd at==8== at 0x488545C: malloc (vg_replace_malloc.c:446)==8== by 0x4006D3: main (double.c:7)Everything is there: the faulty free (line 13), the earlier free of the same block (line 12), and the place where the block was allocated (line 7). On the shifted pointer, Valgrind even tells you by how much: Address 0x4a7f042 is 2 bytes inside a block of size 32 alloc'd. Reading this kind of report in detail is covered in how to read a Valgrind report.
dangling_repair
build_label returns a name#id string, but read from memory it has already given back. The printed text looks right while the code is wrong: the memory check is the real judge. Fix the function so it returns a block it still owns.
free_str_array
Free a NULL-terminated char ** without missing anything: each string, then the array of pointers, in the right order. Freeing the array before the strings leaves the strings unreachable, which is precisely the kind of mistake that leads to a leak or an invalid free.
Frequently asked questions
What does "tcache" mean in the message?
It is the per-thread cache of freed small blocks that glibc has kept since version 2.26. The "2" that follows is hard-coded in the message: you get the same tcache 2 whatever the block size.
Why does the message come after my program's output?
Because glibc only checks consistency at the moment of the free, often after your printf has already produced its output. The order of the lines on screen does not tell you where the bug is; Valgrind points at the line.
134 or 139: what is the difference?
134 (128 + 6) is an abort, the SIGABRT signal glibc raises when it refuses a free. 139 (128 + 11) is a SIGSEGV, a forbidden memory access. A double free gives 134 under glibc, but 139 under musl.
How do I avoid double frees?
Set the pointer to NULL right after freeing it: free(p); p = NULL;. A second free(NULL) does nothing. And free each block in a single place in the code, with clear ownership.
Valgrind says "Invalid free" but my program does not crash. Is it serious?
Yes. An invalid free is undefined behavior: it can go unnoticed once and corrupt the heap the next time. A program that seems to work proves nothing; fix the free.
Sources
