Ruby.EXT.5

ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙


目次


ガベージコレクタと拡張モジュール

 ガベージコレクタ というものをご存じでしょうか? Java や C# などでも採用されている仕組みで、使われなくなったオブジェクト (ガベージ) を自動的に発見して解放してくれる、というものです。C 言語風に言うならば、「自動的に free してくれる仕組み」となるでしょうか。
 Ruby にも、このガベージコレクタが搭載されており、使われなくなったオブジェクトは自動的に回収され、メモリから捨てられて (解放されて) いきます。
 たとえば、次のプログラムで考えてみましょう。

a = "test1"
a = "test2"

とても恣意的なプログラムですが…(笑)。
 上の例に示したプログラムが動作すると、文字列オブジェクト test1 がどこからも参照されなくなってしまいます。そのため、ガベージコレクタが起動すると、どこからも参照されなくなった文字列オブジェクト test1 が回収され、破棄されます。

 このガベージコレクタ、拡張モジュール内で生成された Ruby オブジェクトに対しても有効です。生成されたオブジェクトのうち、どこからも参照されなくなったオブジェクトは破棄され、メモリから捨てられていきます。
 しかし、ここで一つの大きな問題が発生します。「どこからも参照されなくなった」というのは、どのようにして調べればよいのでしょうか?
 Ruby であれば、変数が指し示しているオブジェクトはすべて把握できるわけですから、生成されたすべてのオブジェクトを保有しておき、変数が指し示しているオブジェクトと比較することで、どこからも参照されていない、孤立したオブジェクトを見つけることができます。
 しかし、Ruby は C 言語で書かれた拡張モジュールに関与することができません。たとえば、次のように書かれた拡張モジュールに関して、

VALUE method_foo ( VALUE self )
{
  VALUE str;

  str = rb_str_new2("test");

  return str;
}

変数 str に格納された変数を回収するべきかどうか、どのように知ればよいのでしょうか? 間違って回収してしまったら、その後、解放されたメモリを参照することになってしまいますから、一般保護例外などのエラーが発生する原因になってしまいます。

 Ruby では、この難題を、 スタックフレーム まで調べることで解決しています。
 スタックフレームとは、関数内で使われる変数などを格納する場所のことです。ここを調べれば、関数内でどのオブジェクトが使われているか調べることができるというわけです。しかし、スタックフレームは純粋なメモリですから、変数の境界線がどこにあるか、といった情報など、どこにも書かれていません。つまり、プログラムがスタックフレームを見ると、単に数字が並んでいるようにしか見えないわけです。これでは、いったいどれがオブジェクトを示したものなのか、判別することができません。
 そこで、Ruby は「疑わしきものはすべて疑え!」という方針で、スタックフレームに存在するすべてのデータをオブジェクトへのポインタと認識することにしています。そのため、もしかすると、どこからも参照されていないオブジェクトを「参照されている」と誤認してしまうかもしれません。しかし、少なくとも、参照されているデータはガベージコレクタから保護されるので、プログラムがエラーで停止するようなことは起こりません。

 このガベージコレクタとその挙動を理解することで、拡張モジュール内で Ruby オブジェクトを安全に扱うことができるようになります。以降では、拡張モジュール内で Ruby オブジェクトを生成する方法を説明します。

Ruby オブジェクトの生成

String の作成

 頻繁に使われるオブジェクトとして、文字列オブジェクトの String があります。
 拡張モジュール内で String インスタンスを作成するには、以下の API を使用します。

VALUE rb_str_new (const char * ptr, long len)
ptr 文字列へのポインタ。
len 文字列長。
返値 String インスタンス。

 文字列長を指定するのが面倒な場合は、

VALUE rb_str_new2 (const char * ptr)
ptr 文字列へのポインタ。
返値 String インスタンス。

の方を使用します。ヌル文字が現れるまでの文字列長を調べ、String を作成してくれます。

VALUE obj;
obj = rb_str_new("TEST", 4);
obj = rb_str_new2("TEST");

(2003/02/04)

Array の作成

 配列オブジェクト Array も頻繁に使われるオブジェクトの一つです。
 拡張モジュール内で Array インスタンスを作成するには、以下の API を使用します。

VALUE rb_ary_new ()
返値 Array インスタンス。

 配列は、常に長さと同じ分のバッファを持っているわけではありません。多めのバッファを割り当てておき、要素が追加された場合のオーバヘッドを減らすようにしています。rb_ary_new() では、バッファは Ruby が定めるデフォルトのサイズで初期化されます。しかし、サイズが完全にわかっているような場合、無駄なオーバヘッドを減らすため、サイズを指定して配列オブジェクトを作成することができます。

VALUE rb_ary_new2 (long len)
len 初期バッファ長。
返値 Array インスタンス。

 また、最初から要素がわかっている配列インスタンスを作成する方法もあります。

VALUE rb_ary_new3 (long len, ...)
len 要素数。
返値 Array インスタンス。

 要素数の後には、VALUE を書いていきます。

ary = rb_ary_new3(3, rb_str_new2("a"), rb_fix_new(10), Qnil);

 VALUE の配列から、配列オブジェクトを作成する方法もあります。

VALUE rb_ary_new4 (long len, VALUE *elts)
len 要素数。
elts 格納する Ruby オブジェクトの配列。
返値 Array インスタンス。

 引数で渡した VALUE 配列には、最低限、要素数分のデータが収められていなければなりません。

(2003/02/15)

Hash の作成

 基本的なデータ構造の一つであるため、ハッシュオブジェクト Hash も頻繁に使われるオブジェクトです。
 拡張モジュール内で Hash インスタンスを作成するには、次の API を使用します。

VALUE rb_hash_new ()
返値 Hash インスタンス。

 現在のところ、ハッシュオブジェクトを作成する API は rb_hash_new() だけです。

Numeric の作成

 Ruby で数値を表すオブジェクトはすべて Numeric オブジェクトで表されますが、そのサブクラスまで辿っていくと、BignumFixnumFloat となります。これらの数値オブジェクト、生成するための API がそれぞれで異なります。

VALUE rb_fix_new (int i)
i 変換する数値。
返値 Fixnum インスタンス。

 まずは十分に小さな数値を表す Fixnum を生成するための API です。引数で渡される数値は、十分に小さいことが保証されていなければなりません。自信がない場合や、決められない場合は、後述する INT2NUM() (rb_int2inum()) を使います。

VALUE rb_int2inum (long v)
v 変換する数値。
返値 Fixnum もしくは Bignum インスタンス。

 次は、大きな (多バイト数値も含む) 数値を表す Bignum になってしまうような場合に使われる API です。引数で渡される数値は任意です。どんな場合でも、渡された数を表す Ruby オブジェクトが生成されます。マクロ INT2NUM() も同じ機能を持ちます。どのような数が渡されるかわからない場合は、このメソッドを使った方がいいでしょう。関数の形よりも、マクロの方が多く使われているようです。

VALUE rb_float_new (double d)
d 変換する実数。
返値 Float インスタンス。

 最後は、実数を表す Float を生成するための API です。渡された実数を表すオブジェクトを得ることができます。

(2003/02/23)


ENTRANCEINFORMATION | DIARY | LABORATORY | LINK
CAGE(一般掲示板)Developper's Nest(開発掲示板)こころ宙