Ruby は純粋なオブジェクト指向言語である。 それはつまり存在するすべてのデータがオブジェクトであるということだ。 じゃあオブジェクトってなんなのかと言われそうだが、これはオブジェクト指向の 連載ではないので「Ruby が実装しているソレがオブジェクト」とだけ答えておく。
ではその Ruby のオブジェクトにはどんな特徴があるだろうか。 いくつか挙げてみよう。
Ruby オブジェクトは C レベルでは「VALUE」型で表される。 ruby.h にある VALUE の宣言を以下に示す。
typedef unsigned long VALUE; (ruby.h)見てのとおり unsigned long (最小 32 ビット)だ。だがこのままは使わない。 Ruby では整数もオブジェクトであるが、整数のインスタンスは非常に多いので、 これを (例えば) 構造体として表現してしまうととてつもなく実行が遅くなって しまう恐れがある。そのため Ruby では小さな (と言っても 2 の 30 乗) 整数を 特別扱いして、VALUE の中に「格納」している。VALUE の最下位ビットが 1 なら それは Fixnum のインスタンスである。
さらに、nil、false、true の三つも特別扱いされる。定義は以下の通り。
#define Qfalse 0 #define Qtrue 2 #define Qnil 4 (ruby.h)残りの値 (偶数で Q* でない VALUE) は構造体へのポインタにキャストして使う。 このような仕組みなので、void* と long のサイズが違うと Ruby は動かない。
また 1.5 からは Symbol クラスが加えられ、これも VALUE に直接格納されることに なった。これまでの拡張モジュールはシンボルは Fixnum だと思っているから、 GC されることをまったく考慮していない。だからポインタとして実装するわけには いかなかったというのが実際のところだろう。さて、この Symbol は ID から以下の ように作られる。
#define SYMBOL_FLAG 0x0e #define ID2SYM(x) ((VALUE)(((long)(x))<<8|SYMBOL_FLAG) (ruby.h)0x0e は十進で 14。さらに 8 ビットシフトしているので、シンボルを表す VALUE が 4 の倍数になることはなく、つまり VALUE の指す構造体のアドレスと 偶然同じになることはありえないということになる。
ところで、最下位ビットが 1 (=奇数) の VALUE は Fixnum で、4 の倍数でない 偶数はシンボルの可能性があるのだから、Ruby のオブジェクト(の実体の構造体)は 4 の倍数アドレスにしか配置できない。これは malloc では普通の制約なので特に 気にすることはないのだが、この影響で GC の実装が多少簡単になる。それはあとで 説明しよう。
オブジェクト用の構造体はそのクラスごとに定義されていて、 たとえば Object オブジェクトのための構造体 RObject は以下のようである。
struct RBasic {
unsigned long flags;
VALUE klass;
};
struct RObject {
struct RBasic basic;
struct st_table *iv_tbl;
};
(ruby.h)
オブジェクト用の構造体はすべて「最初に」struct RBasic を持つように
なっている。つまり、これらの構造体はどれもキャストするだけで
struct RBasic にアクセスできる。その中身は以下のとおり。flags は
多目的のフラグ。klass がそのオブジェクトが属するただひとつのクラスである。
klass メンバーは VALUE なので、格納されるのは Ruby のクラスオブジェクト
(Class クラスのインスタンス) である。ちなみに変数名が class でないのは
C++ コンパイラでコンパイルするときに予約語 class と衝突するからだ。
flags メンバーの用途としては、まずその構造体の型を登録するために使う。 型を表すフラグは ruby.h で T_XXXXX という名前で定義されていて、VALUE からは TYPE() マクロで得ることができる。だからたとえば以下のような コードによって型で分岐できる。
switch (TYPE(value)) {
case T_FIXNUM:
/* 整数の処理 */
case T_STRING:
/* 文字列の処理 */
case T_ARRAY:
/* 配列の処理 */
}
nil や false、Fixnum は構造体を持たなかったが、TYPE() の中で
特別扱いにしてちゃんと T_NIL や T_FALSE のような値を返してくれるので
こちらで心配する必要はない。またこの他に freeze や tainted といった
情報もこのフラグに一緒に入っている。
ちなみにこれら構造体はそのクラス自身だけでなく、その下位クラスの インスタンスに対しても使われる。つまり、通常 Ruby スクリプトから定義 するクラスのインスタンスの実体は RObject 構造体であるということだ。 また、String や Array を継承したクラスは String や Array のための 構造体(各クラスの章を参照)を使うということになる。
では今度は Class クラスの構造体を見てみよう。
struct RClass {
struct RBasic basic;
struct st_table *iv_tbl;
struct st_table *m_tbl;
VALUE super;
};
(ruby.h)
super はその名の通りスーパークラスのオブジェクトである。ただし一つ注意。
Ruby は見た目は単一継承なのだが、Module が存在するために単純な単一継承には
ならない。そのため、モジュールがインクルードされると特別な方法で単一継承に
変換される。この詳細はクラスの章で説明する。
Object の構造体にもあった struct st_table は、st.[ch]で定義されている ハッシュ表である。iv_tbl はインスタンス変数 の表で、変数名のIDとオブジェクトの対応が登録 されている。これは Class クラスの構造体だから、このインスタンス表はクラス 自身のインスタンス変数、すなわち Ruby スクリプトで言えば
class A @a = 'instance' endのような場合のインスタンス変数を保持するために使うものである。 また Class のインスタンスでは iv_table はインスタンス変数だけでなく、 定数も同時に保持している。ID は @ や $ の部分まで含めて同一判定をするから、 @ で始まるインスタンス変数と大文字で始まる定数の ID は衝突しない。これを 利用してハッシュを共通化し、メモリを節約している。またこのハッシュは必要に なったときに始めて作成される。
そして m_tbl メンバーがそのクラスが持つメソッドの表で、そのクラスの インスタンスメソッドの名前 (の ID) とメソッドの実体の対応を表している。 ちなみにメソッドの実体とは、構文木の一部 (NODE) である。このことは 構文解析の章で説明する。 このような構造であることがわかれば、メソッド呼びだしを理解するのは簡単だ。 オブジェクトのクラスの m_tbl を探索し、見つからなかったら super の m_tbl を 順々に探索。super がなくなったら (Object でも見つからなかったら)、あきらめて method_missing を呼び出せばよいのだ。この詳細は メソッドの章で解説する。
struct RObject、RClass の他にも、組み込みのよく使われるクラスには それぞれ専用の構造体が用意されている。その詳細は第二部で紹介するが、 ひとつだけ例として Float の実体の構造体を挙げておく。
struct RFloat {
struct RBasic basic;
double value;
};
前に書いたとおり、最初に struct RBasic がある。そして Float の真の
実体には単純に C の double が使われていることがわかる。Ruby 全体を
見ていけばわかるが、eval.c の一部などを除けば Ruby の構造は意外なほど
素直なつくりなのである。Ruby の驚異的な拡張性と実用性はそのへんにも
原因がある。
Rubyのオブジェクトのつくりは今見たとおりなのだが、一つだけ、 拡張モジュールを作るときに使う Data について特に書いておく。
Ruby では C を使って独自に Ruby のクラスを作ることができる。そのクラスにも やはり実体としての構造体が必要になるはずだが、それは作成されるクラスによって 決まるので、あらかじめサイズや構造を知ることができない。そのため Ruby 側では 「ユーザー定義の構造体へのポインタのクラス」を作成し、それを管理するように なっている。その「ポインタのクラス」が Data である。
では、その実体 struct RData を見ていこう。
struct RData {
struct RBasic basic;
void (*dmark)();
void (*dfree)();
void *data;
};
void *data がユーザ定義の構造体へのポインタだ。dmark と dfree は
見てのとおり関数ポインタで、dfree が構造体を解放する時に呼ばれる関数、
dmark はマークアンドスイープの「マーク」を行う関数である。
これは GC の解説を読んでからもう一度考えてみてほしい。
これだけではまだ不完全だ。なぜならこの RData の klass メンバは格納している 構造体が表現するクラスにならないといけないからである。Data を生成している 部分をたどって何をしているのか見てみよう。最初は gc.c の rb_data_object_alloc() だ。
VALUE
rb_data_object_alloc(klass, datap, dmark, dfree)
VALUE klass;
void *datap;
void (*dfree)();
void (*dmark)();
{
NEWOBJ(data, struct RData);
OBJSETUP(data, klass, T_DATA);
data->data = datap;
data->dfree = dfree;
data->dmark = dmark;
return (VALUE)data;
}
(gc.c)
klass がユーザーからうけとった、Data が化けるべきクラスだ。
次に klass が渡っている OBJSETUP を見ると…
#define OBJSETUP(obj,c,t) {\
RBASIC(obj)->klass = (c);\
RBASIC(obj)->flags = (t);\
if (rb_safe_level() >= 3) FL_SET(obj, FL_TAINT);\
}
(ruby.h)
話は実に単純で、data.basic.klass にユーザーが指定したクラスを入れておくだけだ。
ということは、逆に言えば、any.basic.klass のクラス情報はまったくあてにならないと
いうことである。だからこそ、クラスとは別に T_STRING のような構造体の型の情報を
保持しておかなければならないのだ。
Ruby 1.3 からは、組みこみクラスのインスタンスもインスタンス変数を 持てるようになった。しかし、第二部を見てもらえればわかるとおり String や Array の実体はインスタンス変数のテーブルを持っていない(Objectは持っていた)。 それではどうしてインスタンス変数を持てるのだろうか。インスタンス変数を 得る関数 rb_ivar_get を見てみよう。
VALUE
rb_ivar_get(obj, id)
VALUE obj;
ID id;
{
VALUE val;
if (!FL_TEST(obj, FL_TAINT) && rb_safe_level() >= 4)
rb_raise(rb_eSecurityError, "Insecure: can't access instance variable");
switch (TYPE(obj)) {
case T_OBJECT:
case T_CLASS:
case T_MODULE:
if (ROBJECT(obj)->iv_tbl && st_lookup(ROBJECT(obj)->iv_tbl, id, &val))
return val;
break;
default:
if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj))
return generic_ivar_get(obj, id);
break;
}
if (ruby_verbose) {
rb_warning("instance var %s not initialized", rb_id2name(id));
}
return Qnil;
}
(variable.c)
iv_table を持っていた Object Class Module とその他で処理が分かれており、
そこでは generic_ivar_get() を呼んでいる。では generic (汎用)のほうは……?
static st_table *generic_iv_tbl;
static VALUE
generic_ivar_get(obj, id)
VALUE obj;
ID id;
{
st_table *tbl;
VALUE val;
if (!generic_iv_tbl) return Qnil;
if (!st_lookup(generic_iv_tbl, obj, &tbl)) return Qnil;
if (st_lookup(tbl, id, &val)) {
return val;
}
return Qnil;
}
(variable.c)
オブジェクトをキーに st_table を得て、さらにその st_table から検索している。
つまり、最初のインスタンス変数が登録された時点でそのオブジェクト用の
インスタンス表を生成し、オブジェクト→インスタンス表の対応をグローバルな
ハッシュにさらに登録しておくわけだ。
ここまで読むと、なぜ iv_table を直接構造体に作っておかないのかと思うかも しれない。このことの理由は GC と関係がある。
Ruby では、文字列などのデータは通常の malloc で確保するが、オブジェクトの 構造体は gc.c でまとめて管理されている。そのとき、種類がいろいろあったの では扱うのが大変なので、すべての構造体の共用体を宣言して、その配列を管理 することになっているのだ。共用体のサイズはメンバのなかで最大サイズのものと 同じだから、ひとつだけ大きい構造体があったりすると非常に無駄が多くなる。 そして、この共用体の大きさが(現在は)RBasic + ポインタ三つ分なのだ。 インスタンス変数を使う可能性が高いのは String と Array、Hash だろうが、 これらクラスではもうすでに三つ分は他の目的に使われており、どれも外せない。 そこで前述のような間接的な方法で iv_table を持たせているわけだ。
では、この際ポインタ四つ分にしてしまわない理由はなんなのだろうか。 これは単にメモリ効率との兼ね合いだと思う。最も数多く使われるオブジェクト タイプは Object String Array だと思われれるが、Object はポインタひとつ分 しか使わない。つまり、Object のインスタンスが増えるほど無駄なメモリが 増えるわけだ。これがさらにポインタ四つ分になったとすると、Object では ポインタ三つ分の領域が無駄になることになり、看過できません。一方、 iv_table を配置することによって得られるメリットというのは多少のスピード アップであり、しかも頻繁に使われるかどうかはわからない機能だ。このような ことから結局、iv_table のためにオブジェクト構造体のサイズを増やすことは 得にならないと結論できる。
Copyright (c) 1998-2002 Minero Aoki
<aamine@loveruby.net>
This site is link free.