第2章 オブジェクト

Rubyオブジェクトの構造

指針

この章からはいよいよ実際にrubyのソースコードを探索していく。 まずは当初の宣言通りオブジェクトの構造から始めるとしよう。

さて、オブジェクトがオブジェクトとして成立するにはどういう条件が必要だ ろうか。オブジェクトというものの説明は何通りもありうるが、本当に欠かせ ない条件は三つだ。即ち、

である。本章ではこの三つの特徴をこの順番で確認していく。

対象となるファイルは主にruby.hであるが、それに加えobject.cclass.cvariable.cなども軽く眺めつつ進むことになる。

VALUEとオブジェクト構造体

rubyではオブジェクトの実体を構造体で表現し、扱うときは常に ポインタ経由で扱う。構造体のほうはクラスごとに違う型を使うが、 ポインタのほうはどのクラスの構造体でも常にVALUE型だ (図1)。

(value)
図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 RFileIOFileSocketなど
struct RData上記以外の、Cレベルで定義されたクラス全て
struct RStructRubyの構造体Structクラス
struct RBignum大きな整数

例えば文字列オブジェクトならstruct RStringを使うので 図2のようになる。

(string)
図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)

個別に詳しく見るのは後回しにして、全体的な話から始めよう。

まず、VALUEunsigned 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)。

(rbasic)
図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_STRINGstruct RArrayならT_ARRAY、というように 非常に素直に型名と対応している。

struct RBasicのもう一つのメンバklassはそのオブジェクトが属するクラスを 格納している。klassメンバはVALUE型なので、格納されるのはRubyのオブジェ クト(へのポインタ)だ。つまりこれはクラスオブジェクトである(図4)。

(class)
図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)。先に進んで気になったとき 使うために用意しただけなので、すぐに全部理解する必要はない。

(flags)
図5: basic.flagsの使われかた

図を見ると、32ビットマシンなら21ビットの余りがあるようだ。その部分には FL_USER0FL_USER8というフラグが定義されており、構造体ごとに いろいろな目的に使っている。 図にはその例としてFL_USER0FL_SINGLETON)も入れておいた。

VALUE埋め込みオブジェクト

話したとおりVALUEunsigned longである。VALUEはポインタなのだから void*でもよさそうなものだが、そうなっていないのには理由がある。実はVALUEは ポインタでないこともあるのだ。ポインタでないVALUEは以下の六つである。

順番に話していこう。

小さな整数

Rubyでは全データがオブジェクトなので整数もオブジェクトである。 しかし整数のインスタンスは非常に種類が多いので、これを構造体として 表現すると非常に実行が遅くなってしまう恐れがある。例えば0から 50000までインクリメントするような場合、それだけのために50000個の オブジェクトを生成するのはちょっとためらってしまう。

そこでrubyではある程度小さな整数は特別扱いしてVALUEの 中に直接埋め込むようになっている。「小さな」は、 sizeof(VALUE)*8-1ビットに収まる符号付き整数を指す。つまり32ビット マシンなら符号1ビット、整数部分30ビットの整数だ。この範囲の整数なら Fixnumクラス、それ以外の整数ならBignumクラスに所属することになる。

ではCのintFixnumに変換するマクロINT2FIX()を実際に見て、 FixnumVALUEに直接埋め込まれていることを確認してみよう。

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の値と 範囲が重なることはない。

またintlongVALUEに変換するにはこの他にINT2NUM()LONG2NUM()などのマクロも使える。どれも「○○2○○」という名前で、 NUMが入っているとFixnumBignumの両方を扱える。例えば INT2NUM()ならFixnumに収まらないときは勝手にBignumに変換してくれる。 NUM2INT()ならFixnumBignumの両方を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オブジェクトもよくハッシュテーブルのキーに使われたりするためオ ブジェクト数が多い。そこでSymbolFixnumと同じようにVALUEに埋め 込まれることになった。IDSymbolオブジェクトに変換するマクロ 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では 真偽値を返す関数の返り値をVALUEintにしてQtrue/Qfalseを返す、 ということをよくやる。

またQnilのためには、VALUEQnilかどうか判定する専用のマクロ、 NIL_P()がある。

NIL_P()

 170  #define NIL_P(v) ((VALUE)(v) == Qnil)

(ruby.h)

pという名前はLisp由来の記法で、真偽値を返す手続きであること を示している。つまりNIL_Pなら「引数はnilか?」ということだ。 pの字はpredicate(断言する/述語)から来ているらしい。この命名規則は rubyではいろいろなところで使われている。

それと、Rubyではfalsenilが偽で他のオブジェクトは真なのであった。 だがCではnilQnil)は真になってしまう。 そこで、C言語でRuby式の真偽判定をするためのマクロRTEST()も用意されている。

RTEST()

 169  #define RTEST(v) (((VALUE)(v) & ~Qnil) != 0)

(ruby.h)

Qnilは下位2ビットめだけが1なので、~Qnilは下位2ビットめだけが0。 それとbit andして真になるのはQfalseQnilだけである。

!=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_tablerubyの随所で使われているハッシュテーブルだ。詳細は次章『名前と名前表』で 解説するが、とにかく一対一対応の関係を記録するものである。m_tblの場 合なら、このクラスの持つメソッドの名前(ID)とそのメソッドの実体の対 応を記録している。メソッドの実体のつくりについては第二部・第三部で解説する。

次に、四つめのメンバsuperはその名の通りスーパークラスを保持している。 VALUEなのでスーパークラスのクラスオブジェクト(へのポインタ)である。 Rubyではスーパークラスがないクラス(ルートクラス)は唯一Objectだけである。

しかし実際にはObjectのメソッドは全てKernelというモジュールで定義 されており、Objectはそれをインクルードしているだけ、ということは 既に話した。モジュールは機能的には多重継承とほぼ同等なのでsuperだけ ではうまくいかないように思えるのだが、rubyではうまく変換をかけて 単一継承に見せかけている。この操作の詳細は第4章『クラスとモジュール』で説明する。

またこの変換の影響でObjectの構造体のsuperにはKernelの実体の struct RClassが入っており、そしてそのまたsuperNULLになっている。 逆に言うと、superNULLならばそのRClassKernelの実体である (図6)。

(classtree)
図6: Cレベルでのクラスツリー

メソッドの探索

クラスがこのような構造になっているとすればメソッド呼び出しの手順は容易 に想像できる。オブジェクトのクラスのm_tblを探索し、見つからなかったら 次々とsuperm_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_OBJECTT_STRING)を返すマクロで、型フラグはつまり整数 定数なのでswitchで分岐できる。FixnumSymbolは構造体を持たなかっ たが、TYPE()の中で特別扱いにしてちゃんとT_FIXNUMT_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()VALUEstruct 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_tablegeneric_iv_tableを使って解決している(図7)。

(givtable)
図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)objbasic.flagsにフラグFL_EXIVARを 立てるマクロである。basic.flags用のフラグは全てFL_xxxxという 名前になっていて、FL_SET()でフラグを立てられる。その逆のフラグを 落とす操作はFL_UNSET()だ。 またFL_EXIVAREXIVARとはexternal instance variableの略と思われる。

このフラグを立てるのはインスタンス変数の参照を高速化するためだ。 もしFL_EXIVARが立っていなかったらgeneric_iv_tblを索かなくとも インスタンス変数は存在しないことがすぐにわかる。そして当然 ビット検査のほうがstruct st_tableの検索よりも圧倒的に速い。

構造体の隙間

これでインスタンス変数を格納する仕組みはわかったが、どうしてiv_tblのな い構造体があるのだろうか。例えばstruct RStringstruct RArrayには iv_tblがないが、なぜないのだろうか。いっそのことRBasicのメンバにして しまってはだめなのか。

結論から言うと、そういうことはできるが、やるべきではない。実はこの問題 はrubyのオブジェクト管理の仕組みと深く関係している。

rubyでは文字列のデータ(char[])などに使うメモリは直接malloc()で確保す るが、オブジェクト構造体だけは特別扱いで、rubyがま とめて割り当て、そこからもらうようになっている。そのとき構造体型の 種類(というかサイズ)がバラバラだと管理するのが大変なので、全ての構 造体の共用体RVALUEを宣言してその配列を管理しているのだ。共 用体のサイズはそのメンバのなかで最大サイズのものと同じになるから、一つ だけ大きい構造体があったりすると非常に無駄が多くなる。だからできるだけ 構造体の大きさは揃っているのが望ましい。

一番使う構造体とは普通、struct RString(文字列)だろう。その次がプログ ラムによってstruct RArray(配列)、RHash(ハッシュ)、RObject(ユーザ 定義クラス全部)などが来る。ところが困ったことに、このstruct RObjectstruct RBasic + ポインタ一つ分しか使わない。その一方でstruct RStringRArrayRHashは既にstruct Basic + ポインタ三つ分を使っている。つまり struct RObjectが増えるほどポインタ二つ分のメモリが無駄になっていくわけ である。このうえさらにRStringがポインタ四つ分になったりすると、 RObjectは実に共用体のサイズの半分以下しか使わなくなるのだ。これはさすが にもったいない。

一方iv_tblを配置することによって得られるメリットは多少のメモリ 節約とスピードアップであり、しかも頻繁に使われるかどうかはわからない機 能だ。現実にruby 1.2まではgeneric_iv_tblが導入されておらず、従って StringArrayではインスタンス変数を使うことはできなかったのだが、それで もたいして問題にはなっていなかった。その程度の機能のために大量のメモリ を無駄にするのはバカげている。

以上のことを勘案すると、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 RObjectRClassの場合はiv_tblを検索する。先程見たように、 そもそもiv_tblNULLであることもあるので先にチェックしないと落ちる。 そして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)->ptrRSTRING(str)->lenと書くことでptrlenに アクセスできるし、そうしてよい。ただしいくつ注意点がある。

どうしてだろうか。一つにはもちろんソフトウェア工学上の原則がある。人の データを勝手にいじるなということだ。インターフェイス関数があるならそれ を使うべきである。しかしその他にも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にオリジナルのオブジェクトが入る。 ELTSelementsの略のようだ。

というところでRSTRING(str)->ptrの話に戻ろう。 ポインタを参照してもよいのに代入してはいけないのは、 まずlencapaの値が実体と合わなくなること。 それに、リテラルとして作られた文字列を変更するときは aux.sharedを切り離す必要があるからだ。

最後にRStringを扱う例をいくつか書いておく。strRStringを 指す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がその長さ。auxstruct RStringの場合と全く同 じである。aux.capaptrの指すメモリの「本当の」長さであり、 aux.sharedptrを共有している場合に共有元の配列オブジェクトを格納 する。

この構造から明らかなとおりRubyのArrayは配列であって「リスト」ではない。 だから要素数が大きく変わればrealloc()がかかるし、末尾以外に要素を挿入 すればmemmove()が起こる。こんなことをやっていても全くわからないくらい 高速に動いてくれるのだから最近のマシンもたいしたものだ。

それからメンバアクセスの方法もRStringと似ている。 RARRAY(arr)->ptrRARRAY(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は対応付けられていなかったキーを検索したときの値で、デフォルトは niliter_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章『ガ−ベージコレクション』を読んでから改めて 紹介することにする。

(rdata)
図8: struct RDataのイメージ図


御意見・御感想・誤殖の指摘などは 青木峰郎 <aamine@loveruby.net> までお願いします。

『Rubyソースコード完全解説』 はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。

Copyright (c) 2002-2004 Minero Aoki, All rights reserved.