free(): double free detected in tcache の原因と直し方
同じポインタに対してfree()を2回呼び出したときに検出されるエラーです。メモリ管理システムが不整合を検知して強制終了します。
エラーメッセージの読み方
free(): double free detected in tcache 2
free(): double free detected in tcache 2- メモリ管理システム(またはassert)が不整合を検知して出したメッセージ
Aborted- この後プロセスは強制終了します(Aborted)
このエラーが出る典型パターン
パターン1
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 int *p = malloc(sizeof(int)); 6 *p = 1; 7 free(p); 8 free(p); ^ 9 return 0; 10 }
同じポインタに対してfree()を2回呼ぶと、メモリ管理システムが不整合を検知して強制終了します。解放したら即座にNULLを代入しておけば、誤って二重に解放しても検出しやすくなります(NULLに対するfree()は何もしない安全な操作です)。
直し方: free(p); を p = NULL; にします。
パターン2
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 double *p = malloc(sizeof(double)); 6 *p = 1.5; 7 free(p); 8 free(p); ^ 9 return 0; 10 }
型がdoubleでも事情は同じです。1回目のfree()の後もポインタの値自体は残っているため、「まだ使えそう」に見えるのが危険なところです。
直し方: free(p); を p = NULL; にします。
パターン3
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 char *buf = malloc(16); 6 free(buf); 7 free(buf); ^ 8 return 0; 9 }
解放後すぐにNULLを代入する習慣をつけておけば、コードの離れた場所で誤って再度free()されても、NULLへのfree()は無害なので事故を防げます。
直し方: free(buf); を buf = NULL; にします。
パターン4
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 int *n = malloc(sizeof(int)); 6 *n = 5; 7 free(n); 8 free(n); ^ 9 return 0; 10 }
解放したら即座にNULLを代入する習慣で、二重解放を未然に防げます。
直し方: free(n); を n = NULL; にします。
パターン5
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(void) { 5 float *f = malloc(sizeof(float)); 6 *f = 2.5f; 7 free(f); 8 free(f); ^ 9 return 0; 10 }
float型でも同様です。解放後のポインタをそのまま残しておくと再度の解放に気づけません。
直し方: free(f); を f = NULL; にします。
よくある誤解
1回目のfree()の後もポインタの値自体は変わらず残っているため、「まだ使えそうに見える」のが危険なところです。解放したポインタは即座にNULLを代入する習慣をつけないと、コードのどこか離れた場所で誤って2回目のfree()が呼ばれても気づけません。
実務での勘所
「detected in tcache」という文言が出るのは、glibcのメモリ管理(tcacheという高速な再利用の仕組み)が比較的新しいバージョンで、解放したはずのブロックがすでにtcacheのリストに入っていないかを確認するようになったためです。以前のglibcではこの検査が無く、double freeは検出されずに静かにヒープの内部データを破壊し、ずっと後になって無関係な場所でクラッシュするという、原因の特定が非常に困難な不具合になりがちでした。エラーとしてすぐに検出してくれるようになったのは、セキュリティ強化の一環です。