2013年3月26日火曜日

value.h (その3〜 not MRB_NAN_BOXING 〜 構造体と共用体を複合したサブクラスの実現)

mrb_value では,構造体と共用体を複合させることで,複数種類のサブクラスに共通のデータフォーマットを与えています。MRB_NAN_BOXING が偽の場合(not MRB_NAN_BOXING)について説明します。

図1 mrb_value の PIM
図1は mrb_value の PIM をちょっと整理しなおしています。mrb_value のサブクラスの中には値となる属性を持つものがあります。たとえば,MRB_TT_FLOAT (浮動小数点数) , MRB_TT_VOIDP (ポインタ), MRB_TT_FIXNUM (整数) , MRB_TT_SYMBOL (シンボル) です。他のサブクラスも値を持つ場合がありますし,持たない場合もあるようです。

重要な前提がいくつかあります。
  1. まず mrb_value は型を1つだけ持ちます。この場合の型というのは,例の接頭辞 MRB_TT で始まる25種類の列挙型で表される情報のことです。mrb_value が型をもたなかったり,逆に2つ以上の型を持つことはありません。
  2. 次に mrb_value は値を高々1つ持ちます。「高々1つ」というのは,「多くとも1個」すなわち「0個または1個」という意味です。
  3. 型によって値の参照のしかたが一意に決まります。たとえば型が MRB_TT_FLOAT だった場合には値は mrb_float 型の f を参照します。同様にたとえば型が MRB_TT_VOIDP だったときには値は void * 型の p を参照します。
こういう条件を満たす場合の C 言語での実装方法として,構造体と共用体を併用する方法が一般的に用いられます。UML にするとこんな感じです。
図2 mrb_value の PSM
C言語ではこんな感じです(実際のプログラムから少し簡素化しています)。

struct mrb_value {
  union {
    mrb_float f;
    void *p;
    mrb_int i;
    mrb_sym sym;
  } value;
  enum mrb_vtype tt;
}

共用体 (union) は,中括弧 { } で囲まれた変数を同じメモリ領域に配置します。この例だと,mrb_float f と void *p と mrb_int i と mrb_sym sym の4つの変数を1箇所のメモリ領域に強引に配置します。
前提1と前提3により,同じメモリ領域に配置しても混乱することはありません。なぜならば,いったん型が決まってしまったあとは値の参照方法も決まってしまうので,異なる値の参照方法を同時に使うことはないからです。前提2により,mrb_value 1つに対し,共用体 value で確保するメモリ領域は1つだけあれば十分です。
この共用体 value と型を表す tt の2つを構造体 (struct) としてまとめておきます。このようにしておくと,たとえば次のようにして mrb_value から情報を読み書きできます。
  • 変数 v の型を参照する: v.tt
  • 整数 a を格納する: v.tt = MRB_TT_FIXNUM; v.value.i = a;
  • ポインタ ptr を格納する: v.tt = MRB_TT_VOIDP; v.value.p = ptr;