free(): double free detected の原因と直し方(C++のdelete)
newで確保したメモリに対してdeleteを2回呼んでしまったときに出る実行時エラーです。メモリ管理システムが不整合を検知してプロセスを強制終了させます。
エラーメッセージの読み方
free(): double free detected in tcache 2
free(): double free detected in tcache 2- ランタイム(または例外機構、assert)が不整合を検知して出したメッセージ
Aborted- この後プロセスは強制終了します(Aborted)
このエラーが出る典型パターン
パターン1
1 #include <iostream> 2 int main() { 3 int *p = new int(5); 4 delete p; 5 delete p; ^ 6 std::cout << "done" << std::endl; 7 return 0; 8 }
同じポインタに対してdeleteを2回呼ぶと、メモリ管理システムが不整合を検知して強制終了します。解放直後にnullptrを代入しておけば、誤って二重に解放しても実害を防げます(nullptrへのdeleteは安全な無操作です)。
直し方: delete p; を p = nullptr; にします。
パターン2
1 #include <iostream> 2 int main() { 3 int *val = new int(10); 4 delete val; 5 delete val; ^ 6 std::cout << "done" << std::endl; 7 return 0; 8 }
deleteされたポインタはダングリングポインタ(解放済み領域を指したままの変数)になります。それに対して再度deleteを呼ぶと管理情報の不整合が起きます。
直し方: delete val; を val = nullptr; にします。
パターン3
1 #include <iostream> 2 int main() { 3 int *counter = new int(0); 4 delete counter; 5 delete counter; ^ 6 std::cout << "done" << std::endl; 7 return 0; 8 }
「deleteを呼んでも変数自体は残るので、もう一度呼んでも安全」というのは誤解です。ポインタ変数は残っても、指している先のメモリはすでに解放済みです。
直し方: delete counter; を counter = nullptr; にします。
パターン4
1 #include <iostream> 2 int main() { 3 int *v = new int(3); 4 delete v; 5 delete v; ^ 6 std::cout << "done" << std::endl; 7 return 0; 8 }
解放直後にnullptrを代入する習慣で、二重解放を未然に防げます。
直し方: delete v; を v = nullptr; にします。
パターン5
1 #include <iostream> 2 int main() { 3 double *f = new double(1.5); 4 delete f; 5 delete f; ^ 6 std::cout << "done" << std::endl; 7 return 0; 8 }
double型でも同様です。deleteされたポインタはダングリングポインタになります。
直し方: delete f; を f = nullptr; にします。
よくある誤解
「deleteを呼んでも変数自体は残るので、もう一度deleteしても安全」というのは誤解です。deleteされたポインタは解放済みのメモリを指したままになり(ダングリングポインタ)、それに対して再度deleteを呼ぶと管理情報の不整合が起きます。解放直後にポインタをnullptrにしておけば、誤って二重に解放しても実害を防げます。
実務での勘所
この種のバグを構造的に無くす最も確実な方法は、newとdeleteを手で対にするのをやめ、std::unique_ptrのようなスマートポインタに所有権の管理を任せることです。スマートポインタはデストラクタの中で自動的に1回だけdeleteを呼ぶため、プログラマが解放のタイミングを手で管理する必要が無くなり、二重解放という操作自体が構造的に起こり得なくなります。C++11以降のコードでは、生のnew/deleteを直接書く場面は年々少なくなっています。