Boehm GCの紹介
C/C++では、mallocを使ったらfreeし、newしたら忘れずにdeleteしなければならない。
ガベージコレクタを使うと、メモリを解放する手間を省いてくれる。
C言語で使えるメジャーなガベージコレクタは、昔からBoehm GCが有名。
Boehm GCはJava等と同じマークスイープ方式で、循環参照も開放してくれる。
現在は、bdwgc (Boehm-Demers-Weiser Garbage Collector)として、GitHub上に最新のソースがある。
https://github.com/ivmai/bdwgc/
VisualStudioでビルドする方法
せっかくなので、最新のソースを取得しておきたい。
現在(2016-11-10)の最新版は7.7.0。
$ git clone git://github.com/ivmai/bdwgc.git $ cd bdwgc $ git clone git://github.com/ivmai/libatomic_ops.git
ビルドツールにはconfigureも使えるが、cmakeが便利。
C++で使う場合は、以下のおまじないをしておくとよい。
$ cmake -D enable_cplusplus=ON
gc.slnというソリューションファイルができるので、ビルドする。
32bitと64bitを間違えた場合は、cmakeからやり直す。
gcmt-dll.dllとgcmt-dll.libができれば完成。
使ってみる
まず、プロジェクトの設定を行う。
- includeパスに、bdwgc/include のパスを通す。
- libパスに、gcmt-dll.libの場所を追加する。
- 依存するライブラリに、gcmt-dll.libを追加する。
- gcmt-dll.dllをコピーする。
続いて、Cでサンプルプログラムを書いてみる。
#include <windows.h> #include <gc.h> int main() { for (i = 0; i < 1000000; i++) { char *p = GC_malloc(1000000); Sleep(1); } return 0; }
動かしてみると、メモリ使用量が増えないことが分かる。
続いて、C++でサンプルプログラムを書いてみる。
#include <windows.h> #include <gc_cpp.h> int main() { for (i = 0; i < 1000000; i++) { char *p = new(GC) char[1000000]; Sleep(1); } return 0; }
クラスの場合、class gcを継承することで、ガベージコレクト対象となる。
#include <stdio.h> #include <gc_cpp.h> int Hoge_ctor; int Hoge_dtor; class Hoge : public gc { public: Hoge() { Hoge_ctor++; } ~Hoge() { Hoge_dtor++; } }; int main() { for (int i = 0; i < 1000000; i++) { Hoge *h = new Hoge(); } printf("Hoge ctor: %d\n", Hoge_ctor); printf("Hoge dtor: %d\n", Hoge_dtor); return 0; }
実行結果...
Hoge ctor: 1000000 Hoge dtor: 0
メモリは解放されているが、デストラクタが動いていない。
デストラクタを起動して欲しい場合は、gc_cleanupを継承するとよい。
#include <stdio.h> #include <gc_cpp.h> int Hoge_ctor; int Hoge_dtor; class Hoge : public gc_cleanup { public: Hoge() { Hoge_ctor++; } ~Hoge() { Hoge_dtor++; } }; int main() { for (int i = 0; i < 1000000; i++) { Hoge *h = new Hoge(); } printf("Hoge ctor: %d\n", Hoge_ctor); printf("Hoge dtor: %d\n", Hoge_dtor); return 0; }
実行結果...
Hoge ctor: 1000000 Hoge dtor: 999420
gc起動の閾値を超えない限り回収されないので、デストラクタが動く保証はない。
実行されるかどうか分からないので、使いどころはあまりないのかもしれない。
注意すること
Boehm GCの動作原理を知っておかないと、思わぬところで回収済みの領域にアクセスしてしまうことがある。
Boehm GCがマークスイープで探索する範囲は、以下の2つである。
- スタックにあるポインタをrootとして、
- GC_mallocやnew(GC)で確保した領域、gcやgc_cleanupを継承したクラスにあるポインタを辿っていく
上の図でいうと、①と②は回収されないが、③はアクセス可能にも関わらず、回収されてしまう。
普通のmallocと混ぜて使わないほうがよい。