2005.2.27新規作成。(2001.7.14, 2001.7.15 の日記に加筆。)
(2020.1) 追記。
C/C++には、言語仕様としてのガベージコレクションの機能はない。動的に確保したメモリは、自分で、ただ一度だけ解放するようにプログラムを組まなければならない。解放を忘れるとメモリリークが発生し、解放しすぎるとプログラムがクラッシュすることもある。
ガベージコレクタを使うと、メモリを好きなだけ確保するだけでよく、解放は自動的にしてくれる。巨大なメモリを明示的に解放するような場合のほかは、メモリを解放するコードを書く必要はない。
The Boehm-Demers-Weiser conservative garbage collector (Boehm GC) は、C言語用のメジャーなガベージコレクタ。さまざまなソフトウェアがこのライブラリを使っている。しかし、Fedora Core 3には含まれない。
Boehm GCは、保守的GCという方法でガベージコレクトする。定期的にメモリ内を走査し、使われていないと見られるオブジェクトのメモリを回収していく。Boehm GC以外では、boost::shared_ptrが参照カウント方式を用いている。
このページでは、インストール方法のほか、簡単な使い方についても書いてみる。
入手先;A garbage collector for C and C++
2005年2月現在の最新版は、バージョン6.4。
アーカイブを展開したら、configureして、makeするだけ。configureには、次の引数を与えてみた。これでC++用ライブラリがインストールされる。
$ ./configure --enable-cplusplus $ make $ su # make install
既存のプログラムでガベージコレクタを使うようにするには、メモリの確保(や必要があれば解放)の部分を修正しなければならない。
まずは単純なサンプルから。次のファイルは、とにかくメモリをどんどん確保していく。GCを使う場合は、ヘッダー<gc.h>をincludeした上で、メモリの確保をGC_MALLOC()
で行う。
コンパイル・リンクは、次のようにする。-lオプションで、libgc.soをリンクする。
$ gcc -Wall -I/usr/local/include first.c -lgc
実行中にtopコマンドなどで見ると、使用メモリが増えていかないことが分かる。GCを使わずにmalloc()でメモリを確保するようにすると、当たり前だが、どんどん使用メモリが増えていく。
ガベージコレクトさせるには、メモリの確保をBoehm GCにさせる必要がある。やり方は二種類ある。一つは、オブジェクトをnew
演算子で生成するときにplacementを使う。new (GC)
型名 と書く。もう一つは、クラスをclass gcから派生させる。このときは、そのクラスのオブジェクトを生成するときにplacementを書かなくてもいい。
次のサンプルは大きなメモリを確保するオブジェクトを大量に生成する。
上の例では, ガベージコレクタによってオブジェクトが回収されるときに, デストラクタが自動的に呼び出されない。もちろん delete
演算子を使えばデストラクタが呼び出されるが、解放をガベージコレクタに任せたいというモチベイションと相容れない。
メモリ以外のリソース解放については、デストラクタに頼らずに、明示的に行うようにしなければならない。
(2020.1) ソースコードを更新。
ファイルなどメモリ以外の資源を解放しなければならない場合, タイミングが不定でもいいからデストラクタを自動的に呼び出してほしい。
クラスをgc_cleanup
クラスから派生すればよい. そのクラスのオブジェクトがガベージコレクトされるときに自動的にデストラクタが呼び出される。また、定期的にガベージコレクトさせるには、イベントループなどでアイドル状態のときに GC_gcollect()
を呼び出すようにするといいだろう。
配列については、一層難しい。
new
演算子の書き方によって、どのnew
メソッドが呼び出されるかは、次のように決まっている。生成する要素数はnewメソッドには渡らないことに注意。第一引数は, (vtableを含めた) 確保すべきメモリの大きさになっている。
new 演算子の書き方 | 呼び出されるnew メソッド
|
---|---|
new T | void* operator new(sizeof(T)) |
new (2, f) T | void* operator new(sizeof(T), 2, f) |
new T[5] | void* operator new[](sizeof(T) * 5 + magic) |
new (2, f) T[5] | void* operator new[](sizeof(T) * 5 + magic, 2, f) |
これを踏まえて、次のサンプル;
単純に new Hoge[100]
とすると、ガベージコレクト時に、2番目以降のそれぞれの要素についてデストラクタが呼び出されない。C++ の作り上, new[]
メソッドには必要メモリしか渡されないため、いかんともしがたい。
上の例では, placement new構文を使って, ファイナライザを明示的に指定し、さらにファイナライザ内でデストラクタを呼び出すようにしている。一応、各要素についてデストラクタを呼び出すことができるが、この方法は、まったくポータブルではない。
配列を生成する場合は、次のように書くしかないように思う。配列の配列にしてしまって、一つずつオブジェクトを生成する。ダサい。
26| void cpp_test() { 27| Hoge** ary; 28| for (int i = 0; i < 5; i++) { 29| ary = new (GC) Hoge*[2]; 30| ary[0] = new Hoge; 31| ary[1] = new Hoge; 32| } 33| ary = NULL; // test 34| }
最初に戻って、ガベージコレクタを使うからには、デスクトラクタに依存しないように組み立てるほうが良さそうだ。
サイト内関連文書:
外部: