メモリの二重解放
メモリの二重解放の問題点と対策をまとめました。
読み方
- 二重解放
- にじゅうかいほう
概要
メモリの二重解放とは、言葉通り、メモリを二回解放するということです。
プログラムは、メモリを動的に確保し、そして、解放することができます。 C言語で言えば、mallocとfreeです。C++では newとdelete を利用して、メモリの確保と解放を行います。
メモリの二重解放は、言葉で書くと以下のようになります。
- メモリを確保
- メモリを解放
- メモリを解放
具体的にソースコードで示してみます。
int *p = NULL; p = new int; delete p; // 1回目 delete p; // 2回目
このプログラム自体には、意味はありませんが、 ポインタ p に対して、delete を2回やっています。 「1回目」の delete で p のアドレスのメモリを解放しているため、「2回目」の delete は不要です。
この不要な delete があることで、「二重解放」が起きています。
例えば、メモリ確保1で確保したメモリをメモリ解放1で解放します。 メモリ確保2で別の目的でメモリを確保したときに、たまたま、メモリ確保1と同じアドレスが返ってきたときに、メモリ解放2が実行されると、q の保持しているアドレスが解放されてしまいます。
int *p = NULL; int *q = NULL; p = new int; // メモリ確保1 delete p; // メモリ解放1 q = new int; // メモリ確保2 delete p; // メモリ解放2
マルチスレッドなプログラミングで、そのようなことが起きれば、プログラムは予期せぬ動作をするでしょうし、セグメンテーションフォルト(セグメンテーション違反)が起きて、プログラムが終了してしまうでしょう。
実際に二重解放をするプログラムを動かしてみると、コアダンプ(core dump)するといった結果が得られました。
ソースコード
/* * delete_nullpointer.cpp * Copyright (C) 2018 kaoru <kaoru@localhost> */ #include <iostream> int main(int argc, char const* argv[]) { int *p = NULL; p = new int; delete p; delete p; return 0; }
二重解放の問題は、
- プログラムが期待した動作をしない
- セキュリティの問題を引き起こすかもしれない
といったことが考えられます。
コンパイル
c++ delete_nullpointer.cpp
実行例
$ ./a.out *** Error in `./a.out': double free or corruption (fasttop): 0x0000000000c9fc20 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777e5)[0x7f3dc75e77e5] /lib/x86_64-linux-gnu/libc.so.6(+0x8037a)[0x7f3dc75f037a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f3dc75f453c] ./a.out[0x40081d] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f3dc7590830] ./a.out[0x4006d9] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 00:00 1256764 /kaoru/a.out 00600000-00601000 r--p 00000000 00:00 1256764 /kaoru/a.out 00601000-00602000 rw-p 00001000 00:00 1256764 /kaoru/a.out 00c8e000-00cc0000 rw-p 00000000 00:00 0 [heap] 7f3dc0000000-7f3dc0021000 rw-p 00000000 00:00 0 7f3dc0021000-7f3dc4000000 ---p 00000000 00:00 0 7f3dc7570000-7f3dc7730000 r-xp 00000000 00:00 25144 /lib/x86_64-linux-gnu/libc-2.23.so 7f3dc7730000-7f3dc7739000 ---p 001c0000 00:00 25144 /lib/x86_64-linux-gnu/libc-2.23.so 7f3dc7739000-7f3dc7930000 ---p 001c9000 00:00 25144 /lib/x86_64-linux-gnu/libc-2.23.so 7f3dc7930000-7f3dc7934000 r--p 001c0000 00:00 25144 /lib/x86_64-linux-gnu/libc-2.23.so 7f3dc7934000-7f3dc7936000 rw-p 001c4000 00:00 25144 /lib/x86_64-linux-gnu/libc-2.23.so 7f3dc7936000-7f3dc793a000 rw-p 00000000 00:00 0 7f3dc7940000-7f3dc7956000 r-xp 00000000 00:00 1283993 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f3dc7956000-7f3dc7b55000 ---p 00016000 00:00 1283993 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f3dc7b55000-7f3dc7b56000 rw-p 00015000 00:00 1283993 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f3dc7b60000-7f3dc7c68000 r-xp 00000000 00:00 25136 /lib/x86_64-linux-gnu/libm-2.23.so 7f3dc7c68000-7f3dc7c6a000 ---p 00108000 00:00 25136 /lib/x86_64-linux-gnu/libm-2.23.so 7f3dc7c6a000-7f3dc7e67000 ---p 0010a000 00:00 25136 /lib/x86_64-linux-gnu/libm-2.23.so 7f3dc7e67000-7f3dc7e68000 r--p 00107000 00:00 25136 /lib/x86_64-linux-gnu/libm-2.23.so 7f3dc7e68000-7f3dc7e69000 rw-p 00108000 00:00 25136 /lib/x86_64-linux-gnu/libm-2.23.so 7f3dc7e70000-7f3dc7fe2000 r-xp 00000000 00:00 210954 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f3dc7fe2000-7f3dc7fef000 ---p 00172000 00:00 210954 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f3dc7fef000-7f3dc81e2000 ---p 0017f000 00:00 210954 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f3dc81e2000-7f3dc81ec000 r--p 00172000 00:00 210954 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f3dc81ec000-7f3dc81ee000 rw-p 0017c000 00:00 210954 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f3dc81ee000-7f3dc81f2000 rw-p 00000000 00:00 0 7f3dc8200000-7f3dc8226000 r-xp 00000000 00:00 25138 /lib/x86_64-linux-gnu/ld-2.23.so 7f3dc8425000-7f3dc8426000 r--p 00025000 00:00 25138 /lib/x86_64-linux-gnu/ld-2.23.so 7f3dc8426000-7f3dc8427000 rw-p 00026000 00:00 25138 /lib/x86_64-linux-gnu/ld-2.23.so 7f3dc8427000-7f3dc8428000 rw-p 00000000 00:00 0 7f3dc8530000-7f3dc8531000 rw-p 00000000 00:00 0 7f3dc8540000-7f3dc8541000 rw-p 00000000 00:00 0 7f3dc8550000-7f3dc8552000 rw-p 00000000 00:00 0 7f3dc8560000-7f3dc8561000 rw-p 00000000 00:00 0 7f3dc8570000-7f3dc8571000 rw-p 00000000 00:00 0 7f3dc8580000-7f3dc8581000 rw-p 00000000 00:00 0 7ffff7432000-7ffff7c32000 rw-p 00000000 00:00 0 [stack] 7ffff839e000-7ffff839f000 r-xp 00000000 00:00 0 [vdso] [2] 275 abort (core dumped) ./a.out
対策
二重解放問題の対策は、古くから、解放したポインタに NULL を代入することでした。
int *p = NULL; p = new int; delete p; // 1回目 p = NULL;
このテクニックは、C言語時代から行われていたことです。
C++11以降であれば、nullptr を利用するべきです。
int *p = nullptr; p = new int; delete p; // 1回目 p = nullptr;
C言語時代から古くから行われいたテクニックの1つは、マクロ(define)で解放と代入を行うことです。
#define FREE(p) {free(p);p=NULL;} int *p = NULL; p = malloc(sizeof(int)); FREE(p);
同じようなことを考えると DELETE を define したくなるのではないでしょうか。
#define DELETE(p) {delete p;p=nullptr;} int *p = NULL; p = new int; DELETE(p);
まとめ
メモリを解放したらポインタに nullptr を代入しておく。
関連項目
ツイート