• 2007-09-19

    管窥Ruby——类的变量(更新版)

    版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
    http://www.blogbus.com/dreamhead-logs/8338960.html

    管窥Ruby——类的变量》写在去年,写成之后便更新了一次,因为最初的描述存在一些偏差。即便如此,jxb8901依然指出了其中的一些不足。最近,dennis-zane再次提出了这个问题。回过头来仔细品味,确实有些地方写得不是很到位,索性把它重新写过。

    管窥Ruby——类的变量

    变量和方法是面向对象难以割舍的两个重要组成部分。在《管窥Ruby——类的方法》中,我们谈到方法,沿着这条路继续,我们再来看看类中的变量。

    开始之前,我们还是要再次回顾RClass的定义:
    struct RClass {
        struct RBasic basic;
        struct st_table *iv_tbl;
        struct st_table *m_tbl;
        VALUE super;
    };
    (ruby.h)

    如果你看过《管窥Ruby——类的方法》,了解了方法存储方式,变量的存储方式便也一目了然了,同样的st_table,意味着同样的处理方式。

    不得不承认的一点是,在讨论类的方法时,我故意忽略了一个事实:方法分为类的方法和实例的方法两种。如果对其它语言实现有些许了解,我们知道,这两种方法差别仅仅是this(C++或Java的说法),到了底层时,这个差别可以视为无物,可以统一存放。关于这点,有兴趣可以参考一下《深入Java虚拟机》,它的第五章讲解了虚拟机内方法的表现形式。在Ruby中,类的方法和实例的方法并不是存放在一起的,这里定义的实际上是实例方法,而类的方法是定义在类的Singleton类中。

    遇到变量时,我们会碰到同样的问题:类的变量和实例变量是无法统一管理的。因为类的变量只有唯一的一份,而实例变量则是每个实例都有一份。所以,RClass存放的并不是真正实例变量。代码是说明问题的最好方式,下面这段代码说明了如何设置实例变量:

    VALUE
    rb_ivar_set(obj, id, val)
        VALUE obj;
        ID id;
        VALUE val;
    {
        if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4)
            rb_raise(rb_eSecurityError, "Insecure: can't modify instance variable");
        if (OBJ_FROZEN(obj)) rb_error_frozen("object");
        switch (TYPE(obj)) {
          case T_OBJECT:
          case T_CLASS:
          case T_MODULE:
              if (!ROBJECT(obj)->iv_tbl) ROBJECT(obj)->iv_tbl = st_init_numtable();
              st_insert(ROBJECT(obj)->iv_tbl, id, val);
              break;
          default:
              generic_ivar_set(obj, id, val);
              break;
        }
        return val;
    },
    (variable.c)

    抛开前面那些复杂的东西,直接来看switch语句后面的内容。这里存在两种情况,对于存在T_OBJECT、T_CLASS和T_MODULE标记的,变量会写入iv_tbl,而其余的情况则转交 generic_ivar_set处理。iv_tbl是“实例变量表(instance value table)”的缩写,不过,如果当前对象是个类对象,这个“实例”变量实际上就是类变量,所以,这个名字多少有些名不符实。

    除此之外,还有一个generic_ivar_set。下面是generic_ivar_set的实现:

    static void
    generic_ivar_set(obj, id, val)
        VALUE obj;
        ID id;
        VALUE val;
    {
        st_table *tbl;

        if (rb_special_const_p(obj)) {
            special_generic_ivar = 1;
        }
        if (!generic_iv_tbl) {
            generic_iv_tbl = st_init_numtable();
        }

        if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) {
            FL_SET(obj, FL_EXIVAR);
            tbl = st_init_numtable();
            st_add_direct(generic_iv_tbl, obj, (st_data_t)tbl);
            st_add_direct(tbl, id, val);
            return;
        }
        st_insert(tbl, id, val);
    }
    (variable.c)

    同样忽略一些非主干的部分,我们看到,这段代码先在一个generic_iv_tbl中进行查找,用作查找键值的是对象实例(obj),而目标同样是一个st_table。得到这个表之后,利用变量名做键值将值插入到表中。我们便不难分析在这种做法中实例变量的存储方式。存在一个全局的实例变量表,走到这里的实例都在其中拥有一席之地,而实例变量存储在实例对应的表中。Ruby通过这样一个二级结构,解决了这些实例变量存储的问题。

    了解过基本的做法之后,随之而来的一个问题就是,这些代码都会在什么情况下起作用。

    代码已经说得很明白了,只有在有那几个标记的情况下,才会直接调用iv_tbl。T_CLASS和T_MODULE都很好理解,那什么情况下会有T_OBJECT呢?在C Ruby中,在Ruby层次上定义的类,生成的实例都是会标有T_OBJECT(参见《管窥Ruby——Allocator》)。所以,所有由Ruby层次上生成的对象都会走到这里来。

    那generic_ivar_set呢?除了那几个标志外的其他部分都会走到这里。除此之外的标志表示什么呢?读一下代码我们便不难发现,几乎就是builtin的几个类,比如数组、字符串、正则表达式等等。那为什么这些builtin类没有一个iv_tbl。对于这些builtin而言,它们真正的实例变量都是以C的形式给出,所以,额外存在一个iv_tbl实际上是一种空间上的浪费。

    虽然不是常态,但我们依然可以为这些builtin类添加自己的实例成员。为了保持Ruby的动态特性,这才有了generic_ivar_set的存在。

    分享到:
    引用地址:

    评论

  • HI Zheng,

    I would like to read your blog but I cant read chinese.. Can you post an english version. and syndicate it too...



    Thanks

    Sudhindra
    回复Sudhindra说:
    Hi, Sudhindra!

    Thank you for you interest!
    You can try Google translate or Yahoo babel fish.
    2007-09-20 10:39:52