備忘録的な

ゆる〜く技術全般。

RubyのObjectについて

Ruby で扱える全ての値はオブジェクト

とはよく言われるけど、これが実際には何なのか。
Rubyのオブジェクトチョットワカルって言いたいので調べた。

すごい長くなりそうなので(オブジェクトといっても構造体が複数種あるため)
まずは、自分たちが定義したクラスから生成されるオブジェクトの実体が何なのかから。

結論から言うと
クラス(RClass構造体)から生成されたオブジェクトの実体はRObject構造体である

???と言う事で、実際のコードを見てみる

// ruby/include/ruby/internal/core/robject.h#L48-L58
struct RObject {
    struct RBasic basic;
    union {
        struct {
            uint32_t numiv;
            VALUE *ivptr;
            void *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */
        } heap;
        VALUE ary[ROBJECT_EMBED_LEN_MAX];
    } as;
};

これだけ見ると割とシンプル

  • RBasic・・・次で説明
  • numiv・・・インスタンス変数の数
  • ivptr・・・インスタンス変数の値へのポインタ。あくまで変数名は格納されず値のみ
  • iv_index_tbl・・・インスタンス変数の変数名とivptrの配列内の値を対応させるハッシュテーブル(st_table)へのポインタを格納。変数名はRClass構造体に格納される。

RBasicは

RBasic {
    VALUE flags;                /**< @see enum ::ruby_fl_type. */
    const VALUE klass;

#ifdef __cplusplus
  public:
    RBIMPL_ATTR_CONSTEXPR(CXX11)
    RBIMPL_ATTR_ARTIFICIAL()
    RBIMPL_ATTR_FORCEINLINE()
    RBIMPL_ATTR_NOALIAS()
    /**
     * We need to define this explicit constructor because the field `klass` is
     * const-qualified above,  which effectively  defines the  implicit default
     * constructor as "deleted"  (as of C++11) --  No way but to  define one by
     * ourselves.
     */
    RBasic() :
        flags(RBIMPL_VALUE_NULL),
        klass(RBIMPL_VALUE_NULL)
    {
    }
#endif
};

これまたシンプルでRBasicはflagsklassの2つの値を持っている
flagsは、内部で多目的に使われているんですが、例えば構造体の型(後述のRStringやRArrayとか)を識別するのにも使っています。
klassは、オブジェクトが何のインスタンスであるかを示すクラスポインタ。

以上をふまえて、超ざっくりで言うと
オブジェクト(RObject構造体)は、クラスへのポインタとインスタンス変数の集合体
なので、Class内に定義したmethodsはオブジェクトには含まれていないです。

... 具体的によくわかんねーよって感じになると思うので
実際のRubyコードと対応させて説明してみる。

class Person
    attr_accessor :name, :age
end

上のシンプルなクラスからオブジェクトを生成する。

irb#1(main):001:0> hoge = Person.new
=> #<Person:0x007fe70187f7e0>

irb#1(main):002:0* hoge.name = 'john'
irb#1(main):003:0> hoge.age = 22

irb#1(main):004:0> hoge
=> #<Person:0x007fe70187f7e0 @name="john", @age=22>

この時の、#<Person:0x007fe70187f7e0>部分のPerson はクラスポインタの値で、16進数の0x007fe70187f7e0 はオブジェクトのポインタを示していて、これはオブジェクト毎にユニーク。
この生成されたオブジェクト毎にユニークなポインタがあるので、同じClassから生成されたオブジェクトが複数あってもオブジェクト毎に異なる値を追跡出来ているんですね

で、これらを対応させると...
RObject内のRBasicのklassはPersonを示す、RClass構造体へのポインタ。
RObject内のRBasicのflagsは色々なフラグがあるけど、例えばRObject構造体を示すフラグとか。
numivは2。
ivptrはjohnと22
iv_index_tblは@name, @ageとjohn,22を対応させるハッシュテーブルへのポインタ。
となる。

ここまでで、クラスから生成されたオブジェクトの実体についてざっくり説明したが、文字列や整数とかがRObject構造体と同じなのかと言うとそうではなくてまた別の構造体を使っています。
例えば文字列の値はRString構造体に保存していて、これまたオブジェクトなので文字列にインスタンス変数を持つことも出来ます。
※整数についてはまた話が長くなるので割愛

使う機会はないだろうけど、こんなことも出来ます。

irb#1(main):001:0> str = 'john'
=> "john"
irb#1(main):002:0> str.instance_variable_set('@age', 22)
=> 22
irb#1(main):003:0> str.instance_variables
=> [:@age]

あと厳密に言うと、RObject構造体と同じようにRBasicは共通なんだけど、RStringはivptrやらiv_index_tblは持っていないのでgeneric_iv_tblを使っています。

// ruby/include/ruby/internal/core/rstring.h#L73-L86

struct RString {
    struct RBasic basic;
    union {
        struct {
            long len;
            char *ptr;
            union {
                long capa;
                VALUE shared;
            } aux;
        } heap;
        char ary[RSTRING_EMBED_LEN_MAX + 1];
    } as;
};
// ruby/variable.c#L1217-L1231

static void
generic_ivar_set(VALUE obj, ID id, VALUE val)
{
    struct ivar_update ivup;

    ivup.iv_extended = 0;
    ivup.u.iv_index_tbl = iv_index_tbl_make(obj);
    iv_index_tbl_extend(&ivup, id);
    st_update(generic_iv_tbl, (st_data_t)obj, generic_ivar_update,
          (st_data_t)&ivup);

    ivup.u.ivtbl->ivptr[ivup.index] = val;

    RB_OBJ_WRITTEN(obj, Qundef, val);
}

クラスから生成したオブジェクトの話からちょっと逸れたけど、おわり。





参考

https://github.com/ruby/ruby
http://i.loveruby.net/ja/rhg/book/object.html
Rubyのしくみ -Ruby Under a Microscope-