LoginSignup
1
1

More than 5 years have passed since last update.

【日々更新中です】RubyのRObjectの構造体について深めてみる(2016/06/03更新)

Last updated at Posted at 2016-05-31

最近ひたすらRubyの元コードを読んで、う〜んと頭をもたげています。
頑張って、早くRubyに対してCommit出来るくらいの知識を身につけたいと思います。(そして、いつの日かコミッターとやらになるぞー!おー!)

今回の趣旨

今回の趣旨は、書きながら説明しながらだと適当に読み飛ばすこともないので、勉強にはもってこいだなって感じで、書いてます。だから、自分の理解用のものなのでわかりにくいところや、これは理解が違うってところがあるかもしれませんが、そしたら叱ってやってください。

ちなみにC言語の知識もそこまでないので、勉強しながら調べながら進んでいきます。
寄り道もたくさんします。

rubyのVersionは2.3.1です。

今回のコードはこちら

ruby-2.3.1/include/ruby/ruby.h
struct RObject {
    struct RBasic basic;
    union {
    struct {
        long numiv; /* only uses 32-bits */
        VALUE *ivptr;
            void *iv_index_tbl; /* shortcut for RCLASS_IV_INDEX_TBL(rb_obj_class(obj)) */
    } heap;
    VALUE ary[ROBJECT_EMBED_LEN_MAX];
    } as;
};

これは、RObjectの構造体を定義している部分です。
RObjectのメンバ変数は

  • RBasic basic
  • union(struct{long numiv, VALUE *ivptr, void *iv_index_tbl})
  • VALUE ary[ROBJECT_EMBED_LEN_MAX];

の三つです。
まだまだ全然意味がわかりません笑
勉強していきます

RBasic

まず、RBasicを見てみます。もうどの構造体もメンバに持っていないので
どうやら、RBasicがrubyが持つ構造体の元になっているようです。

ruby-2.3.1/include/ruby/ruby.h
struct RBasic {
    VALUE flags;
    const VALUE klass;
}

RBasicは、VALUE flagsとconst VALUE klassをメンバに持つ構造体でした。

VALUE型

まず、VALUE型は、再定義された型なので調べると

typedef unsigned long VALUE;

と出てきます。
つまり、VALUE型は、32bitで、符号なし(倍長)整数型で、範囲を0~4294967295これだけ持つ型です。
Rubyだとなんかめっちゃ出てきます。

flagsの持つ意味

さて、ではflagsが持つ意味について深めていきます。
flagsは、

flagsは多目的のフラグだが、最も重要な用途は構造体の型 (struct RObjectだとか)を記憶しておくことである。型を示すフラグは T_xxxxという名前で定義されていて、VALUEからはマクロTYPE()で 得ることができる。

Rubyソースコード完全解説
http://i.loveruby.net/ja/rhg/book/

とのことです。
flagsは構造体の型を記憶して、引っ張り出してくるためのものみたいですね。
確かに、RBasicという元祖の構造体に定義してあるので、RBasicをメンバに持つ構造体は、RBasicのflagsにアクセスすれば、何の型なのかというのが一瞬でわかって便利ですね。

せっかくなので、マクロTYPEもちゃんと調べておきます。

マクロTYPE

マクロTYPEはここに定義してありました。

ruby-2.3.1/include/ruby/ruby.h-line_538
static inline int rb_type(VALUE obj);
#define TYPE(x) rb_type((VALUE)(x))

上のrb_typeの関数宣言文にある、static inlineは、インライン関数というものらしいです。(初めて聞いた)

先頭に inline という言葉を付けて関数を宣言すると,コンパイラーはそれをヒントにコードをインライン化――関数のコードを呼出し元に展開する。これにより,関数呼び出しのオーバーヘッドが取り除かれ実行が早くなる。

こちらに記載してありました。
http://d.hatena.ne.jp/wocota/20090219/1235058524

つまり、コンパイルするときに呼び出しに行くんじゃなくて、処理部分をここに書いちゃうよ!ってことみたいです。なるほど!

はい!じゃあ、処理部分を見に行きましょう。

ruby-2.3.1/include/ruby/ruby.h--line_1952
static inline int
rb_type(VALUE obj)
{
    if (RB_IMMEDIATE_P(obj)) {
        if (RB_FIXNUM_P(obj)) return RUBY_T_FIXNUM;
        if (RB_FLONUM_P(obj)) return RUBY_T_FLOAT;
        if (obj == RUBY_Qtrue)  return RUBY_T_TRUE;
        if (RB_STATIC_SYM_P(obj)) return RUBY_T_SYMBOL;
        if (obj == RUBY_Qundef) return RUBY_T_UNDEF;
    }
    else if (!RTEST(obj)) {
        if (obj == RUBY_Qnil)   return RUBY_T_NIL;
        if (obj == RUBY_Qfalse) return RUBY_T_FALSE;
    }
    return RB_BUILTIN_TYPE(obj);
}

VALUE型のオブジェクトを渡すと、何やらT_〇〇というTYPEが返ってきそうな感じです。

RB_IMMEDIATE_P(obj)

上から読んで行きましょう。まずは、RB_IMMEDIATE_P(obj)の条件分岐です。
こいつも、マクロみたいです。
ここにありました。

ruby-2.3.1/include/ruby/ruby.h--line_393
#define RB_IMMEDIATE_P(x) ((VALUE)(x) & RUBY_IMMEDIATE_MASK)

ぐぬぬ、、、どういうことや

&ってアドレス演算子じゃないんか!って思って調べたら、ビット演算子っていうのが、あった。
まだまだ知らないことがいっぱいですね笑

&はビットANDっていう演算子らしく、左辺と右辺の同じ位置にあるビットだけ1に残す演算子みたい。if文の条件分岐なのに、true, falseとかじゃないんだね。

具体的には

0000000000000111  = 7
0000000000001110  = 14
----------------
0000000000000110  = 6

みたいな感じらしい。うおお。なんかPCっぽいと感動してます笑

RUBY_IMMEDIATE_MASK

ほいでは、比較する左辺であるRUBY_IMMEDIATE_MASKの調査を開始したいと思います。

これはすぐ近くにありました。

ruby-2.3.1/include/ruby/ruby.h--line_427

/* special constants - i.e. non-zero and non-fixnum constants */
enum ruby_special_consts {
#if USE_FLONUM
    RUBY_Qfalse = 0x00,     /* ...0000 0000 */
    RUBY_Qtrue  = 0x14,     /* ...0001 0100 */
    RUBY_Qnil   = 0x08,     /* ...0000 1000 */
    RUBY_Qundef = 0x34,     /* ...0011 0100 */

    RUBY_IMMEDIATE_MASK = 0x07,
    RUBY_FIXNUM_FLAG    = 0x01, /* ...xxxx xxx1 */
    RUBY_FLONUM_MASK    = 0x03,
    RUBY_FLONUM_FLAG    = 0x02, /* ...xxxx xx10 */
    RUBY_SYMBOL_FLAG    = 0x0c, /* ...0000 1100 */
#else
    RUBY_Qfalse = 0,        /* ...0000 0000 */
    RUBY_Qtrue  = 2,        /* ...0000 0010 */
    RUBY_Qnil   = 4,        /* ...0000 0100 */
    RUBY_Qundef = 6,        /* ...0000 0110 */

    RUBY_IMMEDIATE_MASK = 0x03,
    RUBY_FIXNUM_FLAG    = 0x01, /* ...xxxx xxx1 */
    RUBY_FLONUM_MASK    = 0x00, /* any values ANDed with FLONUM_MASK cannot be FLONUM_FLAG */
    RUBY_FLONUM_FLAG    = 0x02,
    RUBY_SYMBOL_FLAG    = 0x0e, /* ...0000 1110 */
#endif
    RUBY_SPECIAL_SHIFT  = 8
};

ruby_special_constsと書かれるとちょっとテンションが上がりますね笑
rubyのスペシャルな定数とは一体!!!?

って、書いてありますよね。笑

non-zero and non-fixnum constants

0じゃなくて、fixnumじゃない定数のことみたいです。

さてさて、関係あるところだけを切り取って考えましょう。

ruby-2.3.1/include/ruby/ruby.h--line_427
/* special constants - i.e. non-zero and non-fixnum constants */
enum ruby_special_consts {
#if USE_FLONUM
    RUBY_IMMEDIATE_MASK = 0x07,
#else
    RUBY_IMMEDIATE_MASK = 0x03,
#endif
    RUBY_SPECIAL_SHIFT  = 8
};

USE_FLONUM

いちいちつまづく。本題には一体いつたどり着くのだろうか。。
でも、面白いからよしとします笑
#if USE_FLONUMについて調べます。

ruby-2.3.1/include/ruby/ruby.h--line_409
#ifndef USE_FLONUM
#if SIZEOF_VALUE >= SIZEOF_DOUBLE
#define USE_FLONUM 1
#else
#define USE_FLONUM 0
#endif
#endif

#ifndef は #ifdef の逆です。つまり #ifndef MACRONAME ~~ #endif とあったら、 MACRONAME が #define されてない時にかぎり、~~の部分をコンパイルするという事です。
参考サイトhttp://www.geocities.co.jp/SiliconValley/6071/technic/16.html

とりあえず、USE_FLONUMが定義されてないとして、次に進みます。

次はここみたいです。深いなーw

ruby-2.3.1/include/ruby/ruby.h--line_103
#if defined HAVE_UINTPTR_T && 0
typedef uintptr_t VALUE;
typedef uintptr_t ID;
# define SIGNED_VALUE intptr_t
# define SIZEOF_VALUE SIZEOF_UINTPTR_T
# undef PRI_VALUE_PREFIX
#elif SIZEOF_LONG == SIZEOF_VOIDP
typedef unsigned long VALUE;
typedef unsigned long ID;
# define SIGNED_VALUE long
# define SIZEOF_VALUE SIZEOF_LONG
# define PRI_VALUE_PREFIX "l"
#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
typedef unsigned LONG_LONG VALUE;
typedef unsigned LONG_LONG ID;
# define SIGNED_VALUE LONG_LONG
# define LONG_LONG_VALUE 1
# define SIZEOF_VALUE SIZEOF_LONG_LONG
# define PRI_VALUE_PREFIX PRI_LL_PREFIX
#else
# error ---->> ruby requires sizeof(void*) == sizeof(long) or sizeof(LONG_LONG) to be compiled. <<----
#endif

さっきみたいに関係あるところだけ抜き出します。

ruby-2.3.1/include/ruby/ruby.h--line_103
#if defined HAVE_UINTPTR_T && 0
    # define SIZEOF_VALUE SIZEOF_UINTPTR_T

#elif SIZEOF_LONG == SIZEOF_VOIDP
    # define SIZEOF_VALUE SIZEOF_LONG

#elif SIZEOF_LONG_LONG == SIZEOF_VOIDP
    # define SIZEOF_VALUE SIZEOF_LONG_LONG

#else
    # error ---->> ruby requires sizeof(void*) == sizeof(long) or sizeof(LONG_LONG) to be compiled. <<----
#endif

まず最初の条件分岐は、HAVE_UINTPTR_Tが定義されていて、かつ、0だった時ということです。
grepして、HAVE_UINTPTR_Tを探してみると、

include/ruby/ruby.h:#if defined HAVE_UINTPTR_T && 0
win32/Makefile.sub:#define HAVE_UINTPTR_T 1

しか出てきませんでした。
つまり、windows32では1と定義されるので、とりあえずここは関係ないのかな?

#If 0ってC,C++では、#endifまで問答無用でコメントにします
http://blog.seasons.cc/entry/20090504/1241390314

って書いてありました。今回は、elifがあるので、そこまではコメントになっているということみたいです。
つまり、今は使われてない的なやつなのかなと推測しておきます。

次は、if SIZEOF_LONG == SIZEOF_VOIDPの部分です。

SIZEOF_LONGは結構遠いところにありました。

ruby-2.3.1/fiddle/fiddle.c--line_377
rb_define_const(mFiddle, "SIZEOF_LONG",  INT2NUM(sizeof(long)));

ちなみに、fiddleはヴァイオリンとか詐欺とかいう意味です。なぜなんだろう…。。

rb_define_constは名前の通り、定数を定義するものっぽいですよね。

正解っぽいです

void rb_define_const(VALUE klass, const char *name, VALUE val)
クラス klass の定数 name を初期値 val で 定義します。既に同名の定数が定義されていたら警告します。
http://docs.ruby-lang.org/ja/2.3.0/function/rb_define_const.html

つまり今回の場合、SIZEOF_LONGを呼び出すと、INT2NUM(sizeof(long))が呼び出されます。
sizeof(long)は、longのsizeを求める関数です。
INT2NUMは、INT型から、NUM型にキャストする関数です。

longは4byteのはずです。

一回復習

なので、SIZEOF_VOIDPがlongのサイズと同じであれば、SIZEOF_VALUEはlongのサイズとなります。
そして、SIZEOF_VALUEがlongのサイズとすると、SIZEOF_DOUBLEと比較できて、
SIZEOF_DOUBLEの方が小さければ、USE_FLONUMが1となり、
RUBY_IMMEDIATE_MASKは0x07となる。
で、0x07とobjとをビットANDして、
その解が真であれば、if文の中に入っていきます!

では、SIZEOF_VOIDPを調べていきます。

ruby-2.3.1/fiddle/fiddle.c--line_353
rb_define_const(mFiddle, "SIZEOF_VOIDP", INT2NUM(sizeof(void*)));

さっきと同じで、void*のサイズをはかる関数のようです。
何やら、mFiddleは、サイズを測ったりするクラスのようです。また、SIZEOF_〇〇とは、〇〇のサイズを測ってINTからNUMに変換する関数のようです。なんとなくわかってきました。

ポインタは、型によらず4byteのようです。
http://rainbow.pc.uec.ac.jp/edu/program/b1/Ex4-1.htm

よしきました!

# define SIZEOF_VALUE SIZEOF_LONGが実行されます。
SIZEOF_VALUEは4byteになります。

じゃあ、帰ってきてUSE_FLONUMの部分いきましょう

ruby-2.3.1/include/ruby/ruby.h--line_409
#ifndef USE_FLONUM
#if SIZEOF_VALUE >= SIZEOF_DOUBLE
#define USE_FLONUM 1
#else
#define USE_FLONUM 0
#endif
#endif

4byteより、SIZEOF_DOUBLEが大きければ、USE_FLONUMが1となります。

SIZEOF_〇〇は、サイズをはかるやつです。

ruby-2.3.1/fiddle/fiddle.c--line_397
rb_define_const(mFiddle, "SIZEOF_DOUBLE",INT2NUM(sizeof(double)));

doubleは8byteです。ですので、USE_FLONUMは、0となります。

さっきのSIZEOF_VALUEの定義が間違ってました

やばい。SIZEOF_VALUEの定義が間違っていることにここで気づきました。
doubleは8byteで固定なため、SIZEOF_VALUEが可変でないとおかしいはずです。

どこで間違ったのか

6月2日

jkr_2255さんのコメントで判明しました!(ありがとうございました!)

可搬性
Rubyはいろいろな環境でコンパイルできるように、型のサイズを決め打ちにしないようにしてあります。
Windows/Linux x86…ポインタもlongも32ビット
Windows x64…ポインタは64ビット、longは32ビット
Linux x64…ポインタもlongも64ビット
SIZEOF_XXXマクロは、これと似たような仕組みでソースコードの外から供給されますが、基本的には「sizeof(xxx)」の結果と同じ、と考えて問題ありません。
USE_FLONUMも、環境によって0か1かが決まってくるものなので、VALUEが32ビット、doubleが64ビットの環境では0になります。

ということだったので、longが4byte、ポインタも、型によらず4byteって決めつけていたのが間違いでした。

Windows/Linux x86の時は、longもポインタも32bitと同じなので、SIZEOF_VALUEはSIZEOF_LONGと同じと定義され、4byteです。x86のdoubleは8byteでした。(参考:http://d.hatena.ne.jp/bellbind/20081118/1227032924)
なので、#if SIZEOF_VALUE >= SIZEOF_DOUBLEは、falseが買えるので、USE_FLONUMは0になって、RUBY_IMMEDIATE_MASK = 0x03になります。
0x03は16進数で、10進数に直すと、3です。3は2進数に直すと0011です。

この0011とobjのVALUEをビットANDして、この先どおしたらええんや笑

#define RB_IMMEDIATE_P(x) ((VALUE)(x) & RUBY_IMMEDIATE_MASK)

IMMEDIATEは、”直接の、じかの、即座の、即時の、早速の、すぐ隣の、隣接した、当面の、目下の、ごく近い”って意味みたいです。
_Pは、

~pという名前はLisp由来の記法で、真偽値を返す手続きであること を示している。
http://i.loveruby.net/ja/rhg/book/object.html

ということです。

つまり、RB_IMMEDIATE_P(x)は、xが直に何かしてるかどうか?みたいなニュアンス?のようです。
惜しい感じ。

と、思って調べまくってたら、おおおおお!って感じで発見しました。

例えば、第 1 ビットに 1 が入っているとすると、第 1 ビットが立っているという言い方をします。ある数の第 1 ビットが立っているかどうかを調べる時、論理積 ( & ) 演算子を使います。
a = ある数 & 0X02;
上記の演算を行って a が 0 なら、第 1 ビットは立っていません。0 以外なら、第 1 ビットは立っています。
http://www1.cts.ne.jp/~clab/hsample/Bit/Bit2.html

つまり、0011の部分がかぶっていたらtrue!、
どこも被らずに0になったら、false!ということみたいです。

ここで、jkr_2255さんのコメントに書いてあった、

ポインタの下位数ビットが0となるように配置されます。
Rubyではこれを利用して、最下位ビットが立っているものはFixnumとして扱います(参考)。また、4バイトアラインだと「4で割って2余る数」も有効なポインタとして使われないことになるので、ビット数が十分ある状況ならdoubleをシフトして、下位ビットを調整した上でVALUEに突っ込んでしまいます。

が活きてくるような気がします。
まず、Fixnumだったら、最下位ビットが必ず1が立つので、trueです。
中を見てましょう。

if (RB_IMMEDIATE_P(obj)) {
  if (RB_FIXNUM_P(obj)) return RUBY_T_FIXNUM;
  if (RB_FLONUM_P(obj)) return RUBY_T_FLOAT;
  if (obj == RUBY_Qtrue)  return RUBY_T_TRUE;
  if (RB_STATIC_SYM_P(obj)) return RUBY_T_SYMBOL;
  if (obj == RUBY_Qundef) return RUBY_T_UNDEF;
}

どうやら正解みたいです!
if (RB_FIXNUM_P(obj)) return RUBY_T_FIXNUM;があるので、objがFIXNUMの場合、RUBY_T_FIXNUMが返るみたいです。

やりました!

一応、RB_FIXNUM_P(obj)と、RUBY_T_FIXNUMについても勉強しておきます。

RB_FIXNUM_P

ruby-2.3.1/include/ruby/ruby.h--line_382
#define RB_FIXNUM_P(f) (((int)(SIGNED_VALUE)(f))&RUBY_FIXNUM_FLAG)

RUBY_FIXNUM_FLAGは実は、ruby_special_constsの部分で定義していました。
RUBY_FIXNUM_FLAG = 0x01です。2進数だと、0001なので
最下位ビットのみが立っていれば、FIXNUMということになります。

RUBY_T_FIXNUM

ruby-2.3.1/include/ruby/ruby.h--line_472
enum ruby_value_type {
    RUBY_T_NONE   = 0x00,

    RUBY_T_OBJECT = 0x01,
    RUBY_T_CLASS  = 0x02,
    RUBY_T_MODULE = 0x03,
    RUBY_T_FLOAT  = 0x04,
    RUBY_T_STRING = 0x05,
    RUBY_T_REGEXP = 0x06,
    RUBY_T_ARRAY  = 0x07,
    RUBY_T_HASH   = 0x08,
    RUBY_T_STRUCT = 0x09,
    RUBY_T_BIGNUM = 0x0a,
    RUBY_T_FILE   = 0x0b,
    RUBY_T_DATA   = 0x0c,
    RUBY_T_MATCH  = 0x0d,
    RUBY_T_COMPLEX  = 0x0e,
    RUBY_T_RATIONAL = 0x0f,

    RUBY_T_NIL    = 0x11,
    RUBY_T_TRUE   = 0x12,
    RUBY_T_FALSE  = 0x13,
    RUBY_T_SYMBOL = 0x14,
    RUBY_T_FIXNUM = 0x15,
    RUBY_T_UNDEF  = 0x16,

    RUBY_T_IMEMO  = 0x1a,
    RUBY_T_NODE   = 0x1b,
    RUBY_T_ICLASS = 0x1c,
    RUBY_T_ZOMBIE = 0x1d,

    RUBY_T_MASK   = 0x1f
};

TYPEは472行めでたくさん定義してありました。
RUBY_T_FIXNUM = 0x15です。

いやあ、長かったですね。一応、TYPEのゴールに行き着きました。
ついでに、0011でビットANDで真になる他のものを調べてみましょう。

0001(FIXNUM)
0010
0101
0110
0111
1001
1010
1011
1101
1110
1111

が、真になるものとなる気がします。

if (RB_IMMEDIATE_P(obj)) {
  if (RB_FIXNUM_P(obj)) return RUBY_T_FIXNUM;
  if (RB_FLONUM_P(obj)) return RUBY_T_FLOAT;
  if (obj == RUBY_Qtrue)  return RUBY_T_TRUE;
  if (RB_STATIC_SYM_P(obj)) return RUBY_T_SYMBOL;
  if (obj == RUBY_Qundef) return RUBY_T_UNDEF;
}

で、おそらく、このCodeのFIXNUMの下にあるものが、上記の形で表現されているはずです。
調べてあっていたら、理解が正しいということになります。

じゃあ、調べます。

FLONUM

FLONUMどこかで見たぞと思ったけど、USE_FLONUMをさっき定義したところでした。
先ほどは、USE_FLONUMが0になりましたので、今回は使用しないのでスルーします。

RUBY_Qtrue

RUBY_Qtrueも実は、先ほどのruby_special_constsで定義してあります。
RUBY_Qtrue = 2, /* ...0000 0010 */
こう定義されていました。

狙いどうりでしたね!

0001(FIXNUM)
0010(Qtrue)
0101
0110
0111
1001
1010
1011
1101
1110
1111

RUBY_T_SYMBOL

これも、おそらくRUBY_SYMBOL_FLAG = 0x0e, /* ...0000 1110 */の部分と思います。
これも正解みたいです。

0001(FIXNUM)
0010(Qtrue)
0101
0110
0111
1001
1010
1011
1101
1110(SYMBOL_FLAG)
1111

RUBY_Qundef

ラストー!!!
RUBY_Qundef

RUBY_Qundef = 6, /* ...0000 0110 */

これもこのように定義されていました。
これもビンゴです!

0001(FIXNUM)
0010(Qtrue)
0101
0110(Qundef)
0111
1001
1010
1011
1101
1110(SYMBOL_FLAG)
1111

ちなみにQundefはこんな感じのものです

st_delete と似ているが、その場ですぐに削除するのではなく never を
書きこんでおく。st_cleanup_safe() で本当に削除できる。
Ruby では never には Qundef を使う。
http://docs.ruby-lang.org/ja/search/query:st_delete/version:1.8.7/

論理削除に使う型なのかなと推測しておきます。

#define RB_IMMEDIATE_P(x) ((VALUE)(x) & RUBY_IMMEDIATE_MASK)

つまり、RB_IMMEDIATE_P(x) は

  • FIXNUM
  • Qtrue
  • SYMBOL_FLAG
  • Qundef(never)

の時のみtrueになる関数みたいです。

今日も少しですが進みました。
また明日これたらいいなー笑

2016/06/02/03:12


6月3日

今日は始めるのが遅かったので、ちょっとだけ進めます。
昨日のことで間違ってることが少しありました。

0001(FIXNUM)
0010
0101
0110
0111
1001
1010
1011
1101
1110
1111

が、真になるものとなる気がします。

と、このように記述したのですが、冷静に最下位ビットが1のものはFIXNUMとなるので、正しくは

0001(FIXNUM)
0010
0110
1010
1110

となるはずでした。
これに、他の調査した型を当てはめると

0001(FIXNUM)
0010(Qtrue)
0110(Qundef)
1010
1110(SYMBOL_FLAG)

と、このようにピッタリ当てはまります。今日の朝ハッ!っと気づいて、少し嬉しかったです。笑
1010は一体何なのかもきになるところですね。気になって、grepをかけてみたのですが、1010ではhitしませんでした。
1010は、16進数だと0x10なのでこちらで検索してみたら
dir.c:#define FNM_EXTGLOB 0x10
とかが出てきました、、が、あっているかどうかはイマイチわかりません。またわかったら記述します。

ではでは、次に進みます。

ruby-2.3.1/include/ruby/ruby.h--line_1952
static inline int
rb_type(VALUE obj)
{
    if (RB_IMMEDIATE_P(obj)) {
        if (RB_FIXNUM_P(obj)) return RUBY_T_FIXNUM;
        if (RB_FLONUM_P(obj)) return RUBY_T_FLOAT;
        if (obj == RUBY_Qtrue)  return RUBY_T_TRUE;
        if (RB_STATIC_SYM_P(obj)) return RUBY_T_SYMBOL;
        if (obj == RUBY_Qundef) return RUBY_T_UNDEF;
    }

↑ここまでは理解
------------------------------------

    else if (!RTEST(obj)) {
        if (obj == RUBY_Qnil)   return RUBY_T_NIL;
        if (obj == RUBY_Qfalse) return RUBY_T_FALSE;
    }
    return RB_BUILTIN_TYPE(obj);
}

if (!RTEST(obj))

というわけで、次はif (!RTEST(obj))の部分ですね。
こちらは、ruby.hの467行めにありました。

ruby-2.3.1/include/ruby/ruby.h--line_467
#define RTEST(v) !(((VALUE)(v) & ~Qnil) == 0)

vと~QnilのビットANDが0でない時、真って感じですね。
今回の、条件分岐には、!が付いているので、逆でvと~QnilのビットANDが0の時、中に入っていくことになります。

では、~Qnilがどういう意味なのか確認しましょう。
まず、~が何を意味しているのかがわかりません。

C言語やC++言語では、NOT演算子は "~" (チルダ)である。
https://ja.wikipedia.org/wiki/%E3%83%93%E3%83%83%E3%83%88%E6%BC%94%E7%AE%97

なるほど、Qnilのbitが反転するわけですね。
Qnilのbitが反転して、ビットANDして、0になるということは、Qnilのbitと一致しているということになるので
処理部分に入って行って、return Nilされるということですね。なるほど。

では、実際にQnilのbitを確認してみます。

RUBY_Qnil = 4, /* ...0000 0100 */

つまり、NOT演算子(チルダ)で反転すると

1111 1011となるわけです。これとビットANDして0ということは
0000 0100であるわけですから、Qnilで問題ありません。

ここで、問題になるのは、RUBY_Qfalseですよね。
こちらについても調べてみましょう。

RUBY_Qfalse = 0, /* ...0000 0000 */

こう書いてありました。これは盲点でした。
全部、0だったらビットANDシテモ、絶対0になりますよね。

これで、長かったTYPE()関数ともお別れです。

最後は RB_BUILTIN_TYPE(obj);

return RB_BUILTIN_TYPE(obj);

とりあえず、いつも通り調べてみましょう。
ruby.hの534行めにありました。

ruby-2.3.1/include/ruby/ruby.h--line_534
#define RB_BUILTIN_TYPE(x) (int)(((struct RBasic*)(x))->flags & RUBY_T_MASK)

きたー!RBasicのflagsの勉強とビットANDが繋がりました。
今までの努力が身を結びました。

すべてのオブジェクトが持つ構造体である、RBasicのflagsにアクセスして、RUBY_T_MASKとビットANDするわけです。
それで、TYPEがわかるIntを返すと。

ちなみに、RUBY_T_MASKは
RUBY_T_MASK = 0x1fと先ほど定義されていました。

0x1fとは31のことで2進数で表すと
0001 1111のことです。

戻り値であるIntからTYPEを判明するのは、ruby_value_typeを調べればいけそうな予感です。

長かった、、、、笑
これでようやっと、flagsの使い方と必要性が理解できました。

今日はこれくらいにしておきます笑
明日は、flagsの代入部分と、klassについての勉強をしていこうと思いやす。

2016/06/03/03:57


6月4日

さて、今日はflagsの代入部分とklassについて勉強していきます。
とりあえず、flagsの代入部分を探しましょう。

flagsの代入部分

おそらく、何かのインスタンスを作成するときにflagsが設定されるはずなので、
今回は、この関数について追っていこうと思います。

rb_str_new();

これはstring.cにありました。やっと、headerファイルから抜けました笑

ruby-2.3.1/string.c--line_640
static VALUE
str_new0(VALUE klass, const char *ptr, long len, int termlen)
{
    VALUE str;

    if (len < 0) {
    rb_raise(rb_eArgError, "negative string size (or size too big)");
    }

    RUBY_DTRACE_CREATE_HOOK(STRING, len);

    str = str_alloc(klass);
    if (len > RSTRING_EMBED_LEN_MAX) {
    RSTRING(str)->as.heap.aux.capa = len;
    RSTRING(str)->as.heap.ptr = ALLOC_N(char, len + termlen);
    STR_SET_NOEMBED(str);
    }
    else if (len == 0) {
    ENC_CODERANGE_SET(str, ENC_CODERANGE_7BIT);
    }
    if (ptr) {
    memcpy(RSTRING_PTR(str), ptr, len);
    }
    STR_SET_LEN(str, len);
    TERM_FILL(RSTRING_PTR(str) + len, termlen);
    return str;
}

static VALUE
str_new(VALUE klass, const char *ptr, long len)
{
    return str_new0(klass, ptr, len, 1);
}

VALUE
rb_str_new(const char *ptr, long len)
{
    return str_new(rb_cString, ptr, len);
}

rb_cString以外のklassが入ることもあるみたいね。
ちなみに、rb_cStringはここで定義されています。

ruby-2.3.1/string.c--line_9387
rb_cString  = rb_define_class("String", rb_cObject);

せっかくなので、rb_define_classもみておきましょう。
と、思ったのですが、また長くなりそうな雰囲気のあるコードが出てきたので
なので、一旦flagsの代入部分を済ませてから帰ってこようと思います。

とりあえず、コードの流れ的には、
rb_str_new → str_new → str_new0の順番で呼ばれます。
rb_str_newから、rb_cStringをstr_newに渡します。で、そのままstr_new0に渡します。

str_newがある意味ってなんなんだろうかともちょっと思いました笑

では、str_new0のメソッドを見ていきます。
まず最初に文字列の長さチェックがあるみたいです。

if (len < 0) {
    rb_raise(rb_eArgError, "negative string size (or size too big)");
    }

lenが0以下であれば、rb_raise、rb_eArgErrorの例外処理が発生します。
後で、rb_raiseの方も見ていきましょう!
でも、とりあえず今は本筋を進めていきます。

例外処理を超えると、RUBY_DTRACE_CREATE_HOOK(STRING, len);が表れます。
なんだこれはということで調べます。

ruby-2.3.1/include/ruby/ruby.h
#define RUBY_DTRACE_CREATE_HOOK(name, arg) \
    RUBY_DTRACE_HOOK(name##_CREATE, arg)
#define RUBY_DTRACE_HOOK(name, arg) \
do { \
    if (UNLIKELY(RUBY_DTRACE_##name##_ENABLED())) { \
    int dtrace_line; \
    const char *dtrace_file = rb_source_loc(&dtrace_line); \
    if (!dtrace_file) dtrace_file = ""; \
    RUBY_DTRACE_##name(arg, dtrace_file, dtrace_line); \
    } \
} while (0)

コード中に出てくるバックスラッシュはなんだ、一行のコードを改行するやつかな?

rb_raise

rb_define_class

ruby-2.3.1/class.c--line_642
VALUE
rb_define_class(const char *name, VALUE super)
{
    VALUE klass;
    ID id;

    id = rb_intern(name);
    if (rb_const_defined(rb_cObject, id)) {
    klass = rb_const_get(rb_cObject, id);
    if (!RB_TYPE_P(klass, T_CLASS)) {
        rb_raise(rb_eTypeError, "%s is not a class (%"PRIsVALUE")",
             name, rb_obj_class(klass));
    }
    if (rb_class_real(RCLASS_SUPER(klass)) != super) {
        rb_raise(rb_eTypeError, "superclass mismatch for class %s", name);
    }
    return klass;
    }
    if (!super) {
    rb_warn("no super class for `%s', Object assumed", name);
    }
    klass = rb_define_class_id(id, super);
    rb_vm_add_root_module(id, klass);
    rb_name_class(klass, id);
    rb_const_set(rb_cObject, id, klass);
    rb_class_inherited(super, klass);

    return klass;
}


ちなみに、macがどんなbitかわからなかったので、調べてみたら

#include<stdio.h>
int main() {
  printf("long_size is %lu byte\npointer_size is %lu byte\n", sizeof(long), sizeof(void*));
  return 0;
}

long_size is 8 byte
pointer_size is 8 byte

とのように出たので、longもpointerも64bitなので、Linux x64と同じ状態です。


union(struct{long numiv, VALUE *ivptr, void *iv_index_tbl})

VALUE ary[ROBJECT_EMBED_LEN_MAX];

いつもお世話になっています

Rubyソースコード完全解説
http://i.loveruby.net/ja/rhg/book/

ruby-2.3.1/include/ruby/ruby.h
1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1