メモリの二重解放

提供: C++入門
2018年4月21日 (土) 11:44時点におけるDaemon (トーク | 投稿記録)による版

(差分) ←前の版 | 最新版 (差分) | 次の版→ (差分)
移動: 案内検索
スポンサーリンク

メモリの二重解放の問題点と対策をまとめました。

読み方

二重解放
にじゅうかいほう

概要

メモリの二重解放とは、言葉通り、メモリを二回解放するということです。

プログラムは、メモリを動的に確保し、そして、解放することができます。 C言語で言えば、mallocとfreeです。C++では newとdelete を利用して、メモリの確保と解放を行います。

メモリの二重解放は、言葉で書くと以下のようになります。

  1. メモリを確保
  2. メモリを解放
  3. メモリを解放

具体的にソースコードで示してみます。

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 を代入しておく。

関連項目




スポンサーリンク