この章からはいよいよ実際にrubyのソースコードを探索していく。
まずは当初の宣言通りオブジェクトの構造から始めるとしよう。
さて、オブジェクトがオブジェクトとして成立するにはどういう条件が必要だ ろうか。オブジェクトというものの説明は何通りもありうるが、本当に欠かせ ない条件は三つだ。即ち、
である。本章ではこの三つの特徴をこの順番で確認していく。
対象となるファイルは主にruby.hであるが、それに加えobject.c、
class.c、variable.cなども軽く眺めつつ進むことになる。
VALUEとオブジェクト構造体
rubyではオブジェクトの実体を構造体で表現し、扱うときは常に
ポインタ経由で扱う。構造体のほうはクラスごとに違う型を使うが、
ポインタのほうはどのクラスの構造体でも常にVALUE型だ
(図1)。

図1: VALUEと構造体
VALUEの定義はこうだ。
▼VALUE
71 typedef unsigned long VALUE; (ruby.h)
VALUEは実際には各種オブジェクト構造体へのポインタにキャストして使
う。だからポインタとunsigned longのサイズが違うとrubyはうまく動か
ない。厳密に言えば、sizeof(unsigned long)よりも大きいサイズのポイン
タ型があると動かない。さすがに最近はこの条件が成立しないシステムはまず
ないが、昔はそういうマシンが結構あったようだ。
一方の構造体、これには何種類かあり、オブジェクトのクラスによって次のよ うに使い分ける。
struct RObject | 以下にあてはまらないもの全て | ||
struct RClass | クラスオブジェクト | ||
struct RFloat | 小数 | ||
struct RString | 文字列 | ||
struct RArray | 配列 | ||
struct RRegexp | 正規表現 | ||
struct RHash | ハッシュテーブル | ||
struct RFile | IO、File、Socketなど | ||
struct RData | 上記以外の、Cレベルで定義されたクラス全て | ||
struct RStruct | Rubyの構造体Structクラス | ||
struct RBignum | 大きな整数 |
例えば文字列オブジェクトならstruct RStringを使うので
図2のようになる。

図2: 文字列オブジェクトの表現
オブジェクト構造体の定義をいくつか見てみよう。
▼オブジェクト構造体の例
/* 一般のオブジェクトのための構造体 */
295 struct RObject {
296 struct RBasic basic;
297 struct st_table *iv_tbl;
298 };
/* 文字列(Stringのインスタンス)のための構造体 */
314 struct RString {
315 struct RBasic basic;
316 long len;
317 char *ptr;
318 union {
319 long capa;
320 VALUE shared;
321 } aux;
322 };
/* 配列(Arrayのインスタンス)のための構造体 */
324 struct RArray {
325 struct RBasic basic;
326 long len;
327 union {
328 long capa;
329 VALUE shared;
330 } aux;
331 VALUE *ptr;
332 };
(ruby.h)
個別に詳しく見るのは後回しにして、全体的な話から始めよう。
まず、VALUEはunsigned longと定義されているので、ポインタとして使う
ときはキャストしないといけない。そのためにRxxxx()というマクロがオブ
ジェクト構造体ごとに用意されている。例えばstruct RStringならRSTRING()、
struct RArrayならRARRAY()というように。このマクロは次のように使う。
VALUE str = ....; VALUE arr = ....; RSTRING(str)->len; /* ((struct RString*)str)->len */ RARRAY(arr)->len; /* ((struct RArray*)arr)->len */
そして次に、全てのオブジェクト構造体が最初にstruct RBasic型
のメンバbasicを持っていることに注目してほしい。この結果として、
VALUEがどの構造体へのポインタだろうとstruct RBasic*にキャストすれ
ばbasicメンバの内容にアクセスできる(図3)。

図3: struct RBasic
わざわざこういう工夫をしているのだから、struct RBasicにはRubyの
オブジェクトにとってかなり重要な情報が入っているに違いない。
そのstruct RBasicの定義はこうだ。
▼struct RBasic
290 struct RBasic {
291 unsigned long flags;
292 VALUE klass;
293 };
(ruby.h)
flagsは多目的のフラグだが、最も重要な用途は構造体の型
(struct RObjectだとか)を記憶しておくことである。型を示すフラグは
T_xxxxという名前で定義されていて、VALUEからはマクロTYPE()で
得ることができる。例えばこのように。
VALUE str; str = rb_str_new(); /* Rubyの文字列(構造体はRString)を生成 */ TYPE(str) /* 返り値はT_STRING */
このフラグはどれもT_xxxxという名前で、struct RStringなら
T_STRING、struct RArrayならT_ARRAY、というように
非常に素直に型名と対応している。
struct RBasicのもう一つのメンバklassはそのオブジェクトが属するクラスを
格納している。klassメンバはVALUE型なので、格納されるのはRubyのオブジェ
クト(へのポインタ)だ。つまりこれはクラスオブジェクトである(図4)。

図4: オブジェクトとクラス
オブジェクトとクラスの関係についてはこの章の「メソッド」の節で詳しく説明する。
ちなみにメンバ名がclassでないのはC++コンパイラでコンパイルするときに
C++の予約語classと衝突しないようにするためである。
struct Basicのメンバflagsには構造体の型が保存されていると言った。なぜ
構造体の型を保存しておかなければならないのだろうか。それは全ての型の構
造体をVALUE経由で扱うためである。VALUEにキャストしてしまったら変数に型
の情報が残らないのでもうコンパイラは面倒を見てくれない。それゆえ自分で
型を管理しないといけないのだ。これは全ての構造体型を統一的に扱えるこ
との裏返しである。
では、使う構造体はクラスで決まるはずなのに、構造体の型とクラスを 別々に記憶しておくのはどうしてだろうか。クラスから構造体型を求めれば いいのではないだろうか。そうしない理由は二つある。
一つめは、言ったことをいきなり否定して申し訳ないが、実は
struct RBasicを持たない(即ちklassメンバがない)構造体があるのだ。
例えば第二部で登場するstruct RNodeである。しかしこのような特別な構造体
でもflagsだけは最初のメンバに確保されているので、flagsに構造体の型を入
れておけば(今度こそ)全てのオブジェクト構造体を統一的に区分することが
できる。
二つめは、クラスと構造体は一対一に対応しないからだ。例えばユーザが
Rubyレベルで定義したクラスのインスタンスは全てstruct RObjectを使うので、
クラスから構造体型を求めるには結局全てのクラスと構造体の対応を記録して
おかなければならない。それならば構造体に型情報を直接入れておいたほうが
楽で、速い。
basic.flagsの用途
basic.flagsの使い道だが、構造体型「など」という表現では気持ち悪いので
いちおうここで全て図示しておく(図5)。先に進んで気になったとき
使うために用意しただけなので、すぐに全部理解する必要はない。

図5: basic.flagsの使われかた
図を見ると、32ビットマシンなら21ビットの余りがあるようだ。その部分には
FL_USER0〜FL_USER8というフラグが定義されており、構造体ごとに
いろいろな目的に使っている。
図にはその例としてFL_USER0(FL_SINGLETON)も入れておいた。
VALUE埋め込みオブジェクト
話したとおりVALUEはunsigned longである。VALUEはポインタなのだから
void*でもよさそうなものだが、そうなっていないのには理由がある。実はVALUEは
ポインタでないこともあるのだ。ポインタでないVALUEは以下の六つである。
truefalsenilQundef順番に話していこう。
Rubyでは全データがオブジェクトなので整数もオブジェクトである。 しかし整数のインスタンスは非常に種類が多いので、これを構造体として 表現すると非常に実行が遅くなってしまう恐れがある。例えば0から 50000までインクリメントするような場合、それだけのために50000個の オブジェクトを生成するのはちょっとためらってしまう。
そこでrubyではある程度小さな整数は特別扱いしてVALUEの
中に直接埋め込むようになっている。「小さな」は、
sizeof(VALUE)*8-1ビットに収まる符号付き整数を指す。つまり32ビット
マシンなら符号1ビット、整数部分30ビットの整数だ。この範囲の整数なら
Fixnumクラス、それ以外の整数ならBignumクラスに所属することになる。
ではCのintをFixnumに変換するマクロINT2FIX()を実際に見て、
FixnumがVALUEに直接埋め込まれていることを確認してみよう。
▼INT2FIX
123 #define INT2FIX(i) ((VALUE)(((long)(i))<<1 | FIXNUM_FLAG)) 122 #define FIXNUM_FLAG 0x01 (ruby.h)
1ビット左シフトして、1をbit orする。ということはつまり、
110100001000 | 変換前 | ||
1101000010001 | 変換後 |
こんなふうになるわけだ。つまりFixnumであるVALUEは常に奇数
になる。一方Rubyのオブジェクト構造体はmalloc()で確保されるので、
普通は4の倍数アドレスに配置される。だからFixnumであるVALUEの値と
範囲が重なることはない。
またintやlongをVALUEに変換するにはこの他にINT2NUM()・
LONG2NUM()などのマクロも使える。どれも「○○2○○」という名前で、
NUMが入っているとFixnumとBignumの両方を扱える。例えば
INT2NUM()ならFixnumに収まらないときは勝手にBignumに変換してくれる。
NUM2INT()ならFixnumとBignumの両方をintに変換してくれる。
intの範囲に収まらなければ例外を発生するので、こちらで範囲チェックを
したりする必要もない。
シンボルとは何か。
という問いは厄介なので、シンボルが必要になる理由から述べよう。
そもそもrubyの内部で使っているIDという型がある。これだ。
▼ID
72 typedef unsigned long ID; (ruby.h)
このIDは任意の文字列と一対一対応する整数だ。とは言っても、この世に存在
する全ての文字列と数値の一対一対応が決められるわけはない。「rubyの
プロセス一つの中に限って一対一対応」ということである。IDを求める方法
については次章『名前と名前表』で話す。
言語処理系では名前をたくさん扱うことに
なる。メソッド名や変数名、定数名、クラス名にファイル名。そのたくさんあ
る名前を全て文字列(char*)として扱っていたのでは大変だ。何が大変かと
言えばメモリ管理とかメモリ管理とかメモリ管理とかである。それに名前の比
較もきっとたくさん必要になるはずだが、いちいち文字列の比較をしていたの
では速度が落ちる。そこで文字列を直接扱うのではなく、対応する何かを使お
うということになるわけだ。そしてたいてい「何か」は整数になる。一番扱い
が簡単だからだ。
そのIDをRubyの世界に出したのがシンボルだ。ruby 1.4まではIDの値を
そのままFixnumに変換したものがシンボルとして使われていた。今も
Symbol#to_iでその値を得ることができる。しかし実績を積み重ねてきてど
うもFixnumとシンボルを同じにするのはよろしくないということがわかって
きたので、1.6からは独立したクラスSymbolが作られたのである。
Symbolオブジェクトもよくハッシュテーブルのキーに使われたりするためオ
ブジェクト数が多い。そこでSymbolもFixnumと同じようにVALUEに埋め
込まれることになった。IDをSymbolオブジェクトに変換するマクロ
ID2SYM()を見てみよう。
▼ID2SYM
158 #define SYMBOL_FLAG 0x0e 160 #define ID2SYM(x) ((VALUE)(((long)(x))<<8|SYMBOL_FLAG)) (ruby.h)
8ビット左シフトすればxは256の倍数、つまり4の倍数になる。それに
0x0e(十進で14)をbit orする(この場合足すのと同じ)のだからシンボルを
表すVALUEが4の倍数になることはない。しかも奇数でもない。だから他の
VALUEの範囲と重なることはない。なかなかうまい方法だ。
最後にID2SYM()の逆変換、SYM2ID()も見ておこう。
▼SYM2ID()
161 #define SYM2ID(x) RSHIFT((long)x,8) (ruby.h)
RSHIFTは右へのビットシフトである。右シフトはプラットフォームによって
整数の符号が残る・残らないなどの微妙な違いがあるので、マクロになっている。
true false nil
この三つはRubyの特殊オブジェクトである。
それぞれtrueは真の、falseは偽の、代表的な値。
nilは「オブジェクトがない」ことを示すために使われるオブジェクトだ。
これらのCレベルでの値は以下のように定義されている。
▼true false nil
164 #define Qfalse 0 /* Rubyのfalse */ 165 #define Qtrue 2 /* Rubyのtrue */ 166 #define Qnil 4 /* Rubyのnil */ (ruby.h)
今度は偶数だが、0だの2だのがポインタとして使われることはありえないので
他のVALUEの範囲と重なることはない。仮想メモリ空間の最初のブロックは
通常割り当てないからだ。NULLポインタを逆参照したときにプログラムが
すぐに落ちるようにするためである。
それとQfalseは0なのでCレベルでも偽として使える。実際rubyでは
真偽値を返す関数の返り値をVALUEやintにしてQtrue/Qfalseを返す、
ということをよくやる。
またQnilのためには、VALUEがQnilかどうか判定する専用のマクロ、
NIL_P()がある。
▼NIL_P()
170 #define NIL_P(v) ((VALUE)(v) == Qnil) (ruby.h)
〜pという名前はLisp由来の記法で、真偽値を返す手続きであること
を示している。つまりNIL_Pなら「引数はnilか?」ということだ。
pの字はpredicate(断言する/述語)から来ているらしい。この命名規則は
rubyではいろいろなところで使われている。
それと、Rubyではfalseとnilが偽で他のオブジェクトは真なのであった。
だがCではnil(Qnil)は真になってしまう。
そこで、C言語でRuby式の真偽判定をするためのマクロRTEST()も用意されている。
▼RTEST()
169 #define RTEST(v) (((VALUE)(v) & ~Qnil) != 0) (ruby.h)
Qnilは下位2ビットめだけが1なので、~Qnilは下位2ビットめだけが0。
それとbit andして真になるのはQfalseとQnilだけである。
!=0を付け加えているのは確実に0か1にするためである。
glibというライブラリが真偽値に0か1のみを要求するためにこうなっている
([ruby-dev:11049])。
ところでQnilの「Q」は何だろう。RならわかるがなぜQなのか。
聞いてみたところ答えは「Emacsがそうだったから」だそうだ。意外に
楽しくなかった。
Qundef▼Qundef
167 #define Qundef 6 /* undefined value for placeholder */ (ruby.h)
この値はインタプリタ内部で「値が未定義(undefined)である」ことを表すた めに使われる。Rubyレベルには絶対に出てこない(出してはいけない)。
Rubyのオブジェクトの重要な性質として、アイデンティティがあること、 メソッドが呼べること、インスタンスごとにデータを保持できること、 の三点を挙げた。この節ではその第二点、オブジェクトとメソッドを 結び付ける仕組みについて簡単に説明する。
struct RClass
Rubyではクラスもまたオブジェクトとして実行時に存在しているのであった。
ということは当然、クラスオブジェクトの実体となる構造体があるはずだ。
それがstruct RClassである。その構造体型フラグはT_CLASSだ。
またクラスとモジュールはほとんど同じものなので実体を区別する必要はない。
従ってモジュールも構造体にはstruct RClassを使っており、
構造体フラグをT_MODULEにすることで区別している。
▼struct RClass
300 struct RClass {
301 struct RBasic basic;
302 struct st_table *iv_tbl;
303 struct st_table *m_tbl;
304 VALUE super;
305 };
(ruby.h)
まずm_tbl(Method TaBLe)メンバに注目しよう。struct st_tableは
rubyの随所で使われているハッシュテーブルだ。詳細は次章『名前と名前表』で
解説するが、とにかく一対一対応の関係を記録するものである。m_tblの場
合なら、このクラスの持つメソッドの名前(ID)とそのメソッドの実体の対
応を記録している。メソッドの実体のつくりについては第二部・第三部で解説する。
次に、四つめのメンバsuperはその名の通りスーパークラスを保持している。
VALUEなのでスーパークラスのクラスオブジェクト(へのポインタ)である。
Rubyではスーパークラスがないクラス(ルートクラス)は唯一Objectだけである。
しかし実際にはObjectのメソッドは全てKernelというモジュールで定義
されており、Objectはそれをインクルードしているだけ、ということは
既に話した。モジュールは機能的には多重継承とほぼ同等なのでsuperだけ
ではうまくいかないように思えるのだが、rubyではうまく変換をかけて
単一継承に見せかけている。この操作の詳細は第4章『クラスとモジュール』で説明する。
またこの変換の影響でObjectの構造体のsuperにはKernelの実体の
struct RClassが入っており、そしてそのまたsuperはNULLになっている。
逆に言うと、superがNULLならばそのRClassはKernelの実体である
(図6)。

図6: Cレベルでのクラスツリー
クラスがこのような構造になっているとすればメソッド呼び出しの手順は容易
に想像できる。オブジェクトのクラスのm_tblを探索し、見つからなかったら
次々とsuperのm_tblを探索していく。superがなくなったら……つまり
Objectでも見つからなかったら、そのメソッドは定義されていないということ
だ。
以上の手順のうちm_tblの順次検索をやっているのがsearch_method()だ。
▼search_method()
256 static NODE*
257 search_method(klass, id, origin)
258 VALUE klass, *origin;
259 ID id;
260 {
261 NODE *body;
262
263 if (!klass) return 0;
264 while (!st_lookup(RCLASS(klass)->m_tbl, id, &body)) {
265 klass = RCLASS(klass)->super;
266 if (!klass) return 0;
267 }
268
269 if (origin) *origin = klass;
270 return body;
271 }
(eval.c)
この関数はクラスオブジェクトklassからidという名前のメソッドを探す。
RCLASS(value)は
((struct RClass*)(value))
を表すマクロである。
st_lookup()はst_tableからキーに対応する値を検索する関数だ。値が見
付かれば関数全体が真を返し、見付かった値が第三引数に渡したアドレス
(&body)に書き込まれる。このあたりの関数は巻末の関数リファレンスに
載っているので気になったら確認してほしい。
それにしても、毎回この探索をしていたのではいくらなんでも遅すぎるだろう。
そこで現実には一度呼ばれたメソッドはキャッシュされ、二回目からはいちい
ちsuperを辿らずに見付けられるようになっている。そのキャッシュも含めた
探索は第15章『メソッド』で話すことにする。
この節ではオブジェクトの必須条件の第三点、 インスタンス変数の実装について解説する。
rb_ivar_set()
インスタンス変数はオブジェクトごとに固有のデータを保持する仕組みだ。オ
ブジェクトに固有と言うからにはオブジェクト自体に(即ちオブジェクト
構造体に)保存しておくのがよさそうだが、実際にはどうなのだろうか。イン
スタンス変数にオブジェクトを代入する関数rb_ivar_set()を見てみよう。
▼rb_ivar_set()
/* objのインスタンス変数idにvalを代入する */
984 VALUE
985 rb_ivar_set(obj, id, val)
986 VALUE obj;
987 ID id;
988 VALUE val;
989 {
990 if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4)
991 rb_raise(rb_eSecurityError,
"Insecure: can't modify instance variable");
992 if (OBJ_FROZEN(obj)) rb_error_frozen("object");
993 switch (TYPE(obj)) {
994 case T_OBJECT:
995 case T_CLASS:
996 case T_MODULE:
997 if (!ROBJECT(obj)->iv_tbl)
ROBJECT(obj)->iv_tbl = st_init_numtable();
998 st_insert(ROBJECT(obj)->iv_tbl, id, val);
999 break;
1000 default:
1001 generic_ivar_set(obj, id, val);
1002 break;
1003 }
1004 return val;
1005 }
(variable.c)
rb_raise()とrb_error_frozen()はどちらもエラーチェックだ。これからずっ
と言えることだが、エラーチェックというものは現実には必要だが、処理の
本筋ではない。従って初めて読むときにはエラー処理は全て無視するべきで
ある。
エラー処理を削ると残るのはswitch文だけだが、この
switch (TYPE(obj)) {
case T_aaaa:
case T_bbbb:
:
}
というかたちはrubyのイディオムである。TYPE()はオブジェクト構造体の
型フラグ(T_OBJECTやT_STRING)を返すマクロで、型フラグはつまり整数
定数なのでswitchで分岐できる。FixnumやSymbolは構造体を持たなかっ
たが、TYPE()の中で特別扱いにしてちゃんとT_FIXNUMやT_SYMBOLを返し
てくれるのでこちらで心配する必要はない。
さてrb_ivar_set()に戻ろう。T_OBJECT T_CLASS T_MODULEの三つだけ処理
を分けているようだ。
この三つが選ばれた基準は、構造体の第二メンバが
iv_tblだということである。実際に見て確かめよう。
▼第二メンバがiv_tblであるオブジェクト構造体
/* TYPE(val) == T_OBJECT */
295 struct RObject {
296 struct RBasic basic;
297 struct st_table *iv_tbl;
298 };
/* TYPE(val) == T_CLASS or T_MODULE */
300 struct RClass {
301 struct RBasic basic;
302 struct st_table *iv_tbl;
303 struct st_table *m_tbl;
304 VALUE super;
305 };
(ruby.h)
iv_tblはInstance Variable TaBLe、つまりインスタンス変数の
テーブルである。インスタンス変数名とその値の対応を記録している。
rb_ivar_set()のうち、iv_tblがある構造体の場合のコードを再掲しよう。
if (!ROBJECT(obj)->iv_tbl)
ROBJECT(obj)->iv_tbl = st_init_numtable();
st_insert(ROBJECT(obj)->iv_tbl, id, val);
break;
ROBJECT()はVALUEをstruct RObject*にキャストするマクロである。
objが指しているのが実はstruct RClassということもありうるが、第二メ
ンバにだけアクセスしている限り問題は起こらない。
またst_init_numtable()は新しいst_tableを作る関数。
st_insert()はst_tableの中で関連付けを行う関数である。
まとめるとこのコードでやっているのは、iv_tblが存在しなければ作り、
その後「変数名→オブジェクト」の対応を記録する、ということだ。
一つ注意。struct RClassはクラスオブジェクトの構造体だから、
このインスタンス変数テーブルはクラスオブジェクト自身のための
ものだということだ。Rubyプログラムで言えば次のような場合にあたる。
class C @ivar = "content" end
generic_ivar_set()
T_OBJECT T_MODULE T_CLASS以外の構造体を使うオブジェクトに対して
インスタンス変数への代入が起きたときはどうなるのだろうか。
▼rb_ivar_set()−iv_tblがない場合
1000 default: 1001 generic_ivar_set(obj, id, val); 1002 break; (variable.c)
generic_ivar_set()に委譲されている。
関数を見る前にまず大枠を説明しておこう。
T_OBJECT T_MODULE T_CLASS以外の構造体だと構造体にiv_tblメンバがない
(なぜないのかは後で説明する)。しかし構造体メンバがなくともそれ以外の
手段でインスタンスとstruct st_tableを対応付けてやればインスタンス変数
を持てる。rubyではその対応付けをグローバルなst_table、
generic_iv_tableを使って解決している(図7)。

図7: generic_iv_table
では実際に見てみよう。
▼generic_ivar_set()
801 static st_table *generic_iv_tbl;
830 static void
831 generic_ivar_set(obj, id, val)
832 VALUE obj;
833 ID id;
834 VALUE val;
835 {
836 st_table *tbl;
837
/* とりあえず無視してよい */
838 if (rb_special_const_p(obj)) {
839 special_generic_ivar = 1;
840 }
/* generic_iv_tblがなければ作る */
841 if (!generic_iv_tbl) {
842 generic_iv_tbl = st_init_numtable();
843 }
844
/* 本処理 */
845 if (!st_lookup(generic_iv_tbl, obj, &tbl)) {
846 FL_SET(obj, FL_EXIVAR);
847 tbl = st_init_numtable();
848 st_add_direct(generic_iv_tbl, obj, tbl);
849 st_add_direct(tbl, id, val);
850 return;
851 }
852 st_insert(tbl, id, val);
853 }
(variable.c)
rb_special_const_p()は引数がポインタではないときに真になる。
なるのだが、このif文でやっていることはガーベージコレクションについて
知ってからでないと説明できないので、ここでは省略する。
第5章『ガ−ベージコレクション』を読んでから各自探ってみてほしい。
st_init_numtable()は先程も登場した。新しいハッシュテーブルを作る。
st_lookup()はキーに対応する値を検索する。この場合、objに関連付けられて
いるインスタンス変数テーブルを探す。関連付けられている値があれば関数全体は
真を返し、第三引数のアドレス(&tbl)に格納される。
つまり!st_lookup(...)で「値が見付からなかったら」と読むことができる。
st_insert()も既に説明した。テーブルに新しい対応関係を記録する。
st_add_direct()はst_insert()とほとんど同じだが、対応関係を追加する前に
キーが既に記録されているかどうか検査しないところが違う。つまり
st_add_direct()の場合、もし既に登録されているキーを使ってしまうと同
じキーに関する対応関係が二つ記録されてしまうことになる。
st_add_direct()が使えるのは既に存在チェックをした場合とか、
新しいテーブルを作ったばかりのときである。このコードはその条件を
確かに満たす。
FL_SET(obj, FL_EXIVAR)はobjのbasic.flagsにフラグFL_EXIVARを
立てるマクロである。basic.flags用のフラグは全てFL_xxxxという
名前になっていて、FL_SET()でフラグを立てられる。その逆のフラグを
落とす操作はFL_UNSET()だ。
またFL_EXIVARのEXIVARとはexternal instance variableの略と思われる。
このフラグを立てるのはインスタンス変数の参照を高速化するためだ。
もしFL_EXIVARが立っていなかったらgeneric_iv_tblを索かなくとも
インスタンス変数は存在しないことがすぐにわかる。そして当然
ビット検査のほうがstruct st_tableの検索よりも圧倒的に速い。
これでインスタンス変数を格納する仕組みはわかったが、どうしてiv_tblのな
い構造体があるのだろうか。例えばstruct RStringやstruct RArrayには
iv_tblがないが、なぜないのだろうか。いっそのことRBasicのメンバにして
しまってはだめなのか。
結論から言うと、そういうことはできるが、やるべきではない。実はこの問題
はrubyのオブジェクト管理の仕組みと深く関係している。
rubyでは文字列のデータ(char[])などに使うメモリは直接malloc()で確保す
るが、オブジェクト構造体だけは特別扱いで、rubyがま
とめて割り当て、そこからもらうようになっている。そのとき構造体型の
種類(というかサイズ)がバラバラだと管理するのが大変なので、全ての構
造体の共用体RVALUEを宣言してその配列を管理しているのだ。共
用体のサイズはそのメンバのなかで最大サイズのものと同じになるから、一つ
だけ大きい構造体があったりすると非常に無駄が多くなる。だからできるだけ
構造体の大きさは揃っているのが望ましい。
一番使う構造体とは普通、struct RString(文字列)だろう。その次がプログ
ラムによってstruct RArray(配列)、RHash(ハッシュ)、RObject(ユーザ
定義クラス全部)などが来る。ところが困ったことに、このstruct RObjectが
struct RBasic + ポインタ一つ分しか使わない。その一方でstruct RStringや
RArrayやRHashは既にstruct Basic + ポインタ三つ分を使っている。つまり
struct RObjectが増えるほどポインタ二つ分のメモリが無駄になっていくわけ
である。このうえさらにRStringがポインタ四つ分になったりすると、
RObjectは実に共用体のサイズの半分以下しか使わなくなるのだ。これはさすが
にもったいない。
一方iv_tblを配置することによって得られるメリットは多少のメモリ
節約とスピードアップであり、しかも頻繁に使われるかどうかはわからない機
能だ。現実にruby 1.2まではgeneric_iv_tblが導入されておらず、従って
StringやArrayではインスタンス変数を使うことはできなかったのだが、それで
もたいして問題にはなっていなかった。その程度の機能のために大量のメモリ
を無駄にするのはバカげている。
以上のことを勘案すると、iv_tblのためにオブジェクト構造体のサイズを増や
すことは得にならないと結論できる。
rb_ivar_get()
変数にセットするrb_ivar_set()を見たので、参照するほうも軽く見ておこう。
▼rb_ivar_get()
960 VALUE
961 rb_ivar_get(obj, id)
962 VALUE obj;
963 ID id;
964 {
965 VALUE val;
966
967 switch (TYPE(obj)) {
/*(A)*/
968 case T_OBJECT:
969 case T_CLASS:
970 case T_MODULE:
971 if (ROBJECT(obj)->iv_tbl &&
st_lookup(ROBJECT(obj)->iv_tbl, id, &val))
972 return val;
973 break;
/*(B)*/
974 default:
975 if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj))
976 return generic_ivar_get(obj, id);
977 break;
978 }
/*(C)*/
979 rb_warning("instance variable %s not initialized", rb_id2name(id));
980
981 return Qnil;
982 }
(variable.c)
構造は全く同じだ。
(A)struct RObjectかRClassの場合はiv_tblを検索する。先程見たように、
そもそもiv_tblがNULLであることもあるので先にチェックしないと落ちる。
そしてst_lookup()は対応が見付かったときに真を返すのでこのif式はまとめて
「インスタンス変数に既に代入されていたらその値を返す」と読める。
(C)対応が見付からなかったら……つまり代入していないインスタンス変数
を参照したら、ifを抜けてswitchも抜けてその次に行くことになる。そのとき
はrb_warning()で実行時警告を出し、nilを返す。Rubyのインスタンス変数は
代入しなくても参照できるからだ。
(B)一方、構造体がstruct RObjectでもRClassでもないときは、まず
generic_iv_tblからオブジェクトのインスタンス変数テーブルを検索すること
になる。その関数generic_ivar_get()は、想像が付いてしまうので省略する。
それより注目したいのはif文の条件のほうだ。
generic_ivar_set()したオブジェクトにフラグFL_EXIVARが立つということは
先程話した。ここでそのフラグが高速化する役に立っているわけである。
rb_special_const_p()はなんだろう。この関数が真になるのは引数のobjが
構造体を持たないときである。構造体を持たないのならbasic.flagsメンバもない
からそもそもフラグが立てられない。だからFL_xxxx()はそういうオブジェクト
に対しては常に偽を返すようになっているのだ。それゆえここでは
rb_special_const_p()であるオブジェクトを特別扱いしなければならない。
この節ではオブジェクト構造体のうち重要なものの具体的な姿と 扱いかたを簡単に見ていくことにする。
struct RString
struct RStringは文字列クラスStringとその下位クラスのインスタンスの
ための構造体である。
▼struct RString
314 struct RString {
315 struct RBasic basic;
316 long len;
317 char *ptr;
318 union {
319 long capa;
320 VALUE shared;
321 } aux;
322 };
(ruby.h)
ptrが文字列へのポインタ(PoinTeR)で、
lenがその長さ(LENgth)だ。非常に素直である。
Rubyの文字列は文字列というよりバイト列で、NULを含む任意のバイトを含む
ことができる。だからRubyレベルだけ考えるならNULで終端しても意味がない
のだが、Cの関数がNULを要求するので便宜をはかるためとりあえずNUL終端は
してある。ただしその分はlenには含まれない。
またインタプリタや拡張ライブラリから文字列オブジェクトを扱うときは
RSTRING(str)->ptrやRSTRING(str)->lenと書くことでptrとlenに
アクセスできるし、そうしてよい。ただしいくつ注意点がある。
strが本当にstruct RStringを指しているかどうかは先に自分でチェックしなければならないRSTRING(str)->ptrをローカル変数などに保存して後で使ってはいけない
どうしてだろうか。一つにはもちろんソフトウェア工学上の原則がある。人の
データを勝手にいじるなということだ。インターフェイス関数があるならそれ
を使うべきである。しかしその他にもrubyの作りには具体的にポインタを参
照したり保存したりしてはいけない理由があり、それは第四メンバauxと関係
がある。しかしauxの使われかたをちゃんと説明するにはRubyの文字列の特徴
をもう少し説明してからでないといけない。
Rubyの文字列はそれ自体が変化可能(mutable)である。 変化する、とは、
s = "str" # 文字列を生成しaに代入
s.concat("ing") # その文字列オブジェクトに"ing"を追加する
p(s) # "string"が表示される
というようにsの指すオブジェクトそれ自体の内容が「string」になる
いうことである。JavaやPythonの文字列オブジェクトだとこういうことは
ない。強いて言うとJavaのStringBufferが近い。
さてこれがどう関係あるのだろう。まず、変化可能ということは文字列のサイ
ズ(len)が変わる可能性がある。サイズが変わるならその都度メモリ割り当
て量も増やしたり減らしたりしなければならない。それには当然realloc()を使
えばいいが、一般的にmalloc()やrealloc()はかなり重い操作である。文字列を変
更するたびにrealloc()がかかっていたのでは負荷がデカすぎる。
そこでptrの指すメモリは常にlenよりもいくらか長く確保してある。
これによって追加分が余りのメモリで収まるときはrealloc()を呼ばずに済み、
速度を向上できるわけだ。構造体メンバのaux.capaがその余りも含めた
長さを保持している。
ではもう一つのaux.sharedは何だろう。これは文字列リテラルによる文字列
オブジェクトの生成を高速化するための仕組みだ。
次のRubyプログラムを見てほしい。
while true do # 永遠に繰り返す
a = "str" # 内容が「str」である文字列を生成しaに代入
a.concat("ing") # aの指すオブジェクトに直接"ing"を追加する
p(a) # "string"と表示される
end
何回ループを廻ったとしても四行目のpでは"string"と表示されなければな
らない。そのためには"str"という式では毎回別々のchar[]を持つ文字列オブ
ジェクトを作らないといけないはずだ。しかし文字列に対しては全く変更を行
わないことも多いはずで、その場合はchar[]の無駄なコピーがいくつもいくつ
もできることになる。できれば一つのchar[]を共有したいものだ。
その共有を行う仕掛けがaux.sharedである。リテラル式を使って生成された文
字列オブジェクトはどれも一つのchar[]を共有して作成される。そして変更
が起こったときに初めて専用のメモリを割り当てるようにすればいいのである。
共有char[]を使っている時はオブジェクト構造体のbasic.flagsにフラグ
ELTS_SHAREDが立ち、aux.sharedにオリジナルのオブジェクトが入る。
ELTSはelementsの略のようだ。
というところでRSTRING(str)->ptrの話に戻ろう。
ポインタを参照してもよいのに代入してはいけないのは、
まずlenやcapaの値が実体と合わなくなること。
それに、リテラルとして作られた文字列を変更するときは
aux.sharedを切り離す必要があるからだ。
最後にRStringを扱う例をいくつか書いておく。strがRStringを
指すVALUEだと思って読んでほしい。
RSTRING(str)->len; /* 長さ */
RSTRING(str)->ptr[0]; /* 一文字目 */
str = rb_str_new("content", 7); /* 内容が「content」の文字列を生成。
第二引数は長さ */
str = rb_str_new2("content"); /* 内容が「content」の文字列を生成。
長さはstrlen()で計算してくれる */
rb_str_cat2(str, "end"); /* Rubyの文字列にCの文字列を連結 */
struct RArray
struct RArrayはRubyの配列クラスArrayのインスタンスのための構造体である。
▼struct RArray
324 struct RArray {
325 struct RBasic basic;
326 long len;
327 union {
328 long capa;
329 VALUE shared;
330 } aux;
331 VALUE *ptr;
332 };
(ruby.h)
ptrの型以外はstruct RStringとほとんど同じ構造だ。ptrが指す先に配
列の内容があり、lenがその長さ。auxはstruct RStringの場合と全く同
じである。aux.capaがptrの指すメモリの「本当の」長さであり、
aux.sharedはptrを共有している場合に共有元の配列オブジェクトを格納
する。
この構造から明らかなとおりRubyのArrayは配列であって「リスト」ではない。
だから要素数が大きく変わればrealloc()がかかるし、末尾以外に要素を挿入
すればmemmove()が起こる。こんなことをやっていても全くわからないくらい
高速に動いてくれるのだから最近のマシンもたいしたものだ。
それからメンバアクセスの方法もRStringと似ている。
RARRAY(arr)->ptrやRARRAY(arr)->lenでメンバを参照できるし、してよい、
しかし代入はしてはならない、などなど以下同文。簡単な例だけ載せておく。
/* Cから配列を操作する */ VALUE ary; ary = rb_ary_new(); /* 空の配列を作成 */ rb_ary_push(ary, INT2FIX(9)); /* Rubyの9をpush */ RARRAY(ary)->ptr[0]; /* インデックス0を参照 */ rb_p(RARRAY(ary)->ptr[0]); /* ary[0]をpする(結果は9) */ # Rubyから配列を操作する ary = [] # 空の配列を生成 ary.push(9) # 9をpush ary[0] # インデックス0を参照 p(ary[0]) # ary[0]をpする(結果は9)
struct RRegexp
正規表現のクラスRegexpのインスタンスのための構造体。
▼struct RRegexp
334 struct RRegexp {
335 struct RBasic basic;
336 struct re_pattern_buffer *ptr;
337 long len;
338 char *str;
339 };
(ruby.h)
ptrはコンパイル済みの正規表現。
strがコンパイル前の文字列(正規表現のソースコード)で、
lenはその長さだ。
Regexpオブジェクトを扱うコードは本書では登場しないので使いかたは省略
する。拡張ライブラリから使うにしても、よほど特殊な使いかたをしない限り
インターフェイス関数で十分なのでリファレンスがあれば済むだろう。ruby
のC APIリファレンスは添付CD-ROMに入っている
\footnote{ruby C APIリファレンス……添付CD-ROMのarchives/ruby-apiref.tar.gz}。
struct RHash
struct RHashはRubyのハッシュテーブル、
Hashオブジェクトのための構造体である。
▼struct RHash
341 struct RHash {
342 struct RBasic basic;
343 struct st_table *tbl;
344 int iter_lev;
345 VALUE ifnone;
346 };
(ruby.h)
struct st_tableのラッパーである。st_tableについては
次章『名前と名前表』で詳しく解説する。
ifnoneは対応付けられていなかったキーを検索したときの値で、デフォルトは
nil。iter_levはハッシュテーブル自体をリエントラント(マルチスレッドセー
フ)にするための仕掛けである。
struct RFile
struct RFileは組み込みクラスIOと、
その下位クラスのインスタンスのための構造体である。
▼struct RFile
348 struct RFile {
349 struct RBasic basic;
350 struct OpenFile *fptr;
351 };
(ruby.h)
▼OpenFile
19 typedef struct OpenFile {
20 FILE *f; /* stdio ptr for read/write */
21 FILE *f2; /* additional ptr for rw pipes */
22 int mode; /* mode flags */
23 int pid; /* child's pid (for pipes) */
24 int lineno; /* number of lines read */
25 char *path; /* pathname for file */
26 void (*finalize) _((struct OpenFile*)); /* finalize proc */
27 } OpenFile;
(rubyio.h)
メンバが盛大にstruct OpenFileに移されている。IOオブジェクトは
あまりインスタンス数が多くないのでこういうことをしてもよいわけだ。
それぞれのメンバの用途はコメントに書いてある。基本的にCのstdioの
ラッパーである。
struct RData
struct RDataはいままでのものとは趣が違う。
これは拡張ライブラリの実装のために用意されている構造体である。
拡張ライブラリで作成するクラスにもやはり実体としての構造体が必要になる
はずだが、その構造体の型は作成されるクラスによって決まるので、あらかじ
めサイズや構造を知ることができない。そのためruby側では「ユーザー定義
の構造体へのポインタを管理するための構造体」を作成し、それを管理する
ようになっている。その構造体がstruct RDataだ。
▼struct RData
353 struct RData {
354 struct RBasic basic;
355 void (*dmark) _((void*));
356 void (*dfree) _((void*));
357 void *data;
358 };
(ruby.h)
dataがユーザ定義の構造体へのポインタ、
dfreeがその構造体を解放するために使う関数で、
dmarkはマーク&スイープの「マーク」を行う関数である。
struct RDataについてはどうにもまだ説明しづらいので、
とりあえずイメージ図だけ見ておいてもらおう(図8)。
メンバの詳しいことについては第5章『ガ−ベージコレクション』を読んでから改めて
紹介することにする。

図8: struct RDataのイメージ図
御意見・御感想・誤殖の指摘などは 青木峰郎 <aamine@loveruby.net> までお願いします。
『Rubyソースコード完全解説』 はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。
Copyright (c) 2002-2004 Minero Aoki, All rights reserved.