C言語でGarbage Collectorを使う

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と混ぜて使わないほうがよい。