• 2006-10-17

    管窥Ruby——类的变量

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

    UPDATE
    这篇blog的叙述存在一些偏差,所以,又重新写过:《管窥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虚拟机》,它的第五章讲解了虚拟机内方法的表现形式。

    但是,当我们遇到变量时,同样的问题,我们便不能忽略了:类的变量和实例变量是无法统一管理的,因为类的变量只有唯一的一份,而实例变量则是每个实例都有一份。代码是说明问题的最好方式,下面这段代码说明了如何设置一个实例变量:

    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语句后面的内容。在这里,我们看到只有在特定的情况下,才会向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就是用了这样一个二级结构,解决了实例变量存储的问题。

    UPDATE
    事实上,上面的说法并不完全正确,rb_ivar_set中真正的主干是T_OBJECT,因为在Ruby层次上定义的类,走的都是这条路,这一点可以从初始化的代码中看到。

    另外,上面没有提到类变量如何处理,实际上,在Ruby中,类也是对象,类的变量实际上就是类对象的实例变量。

    分享到:

    历史上的今天:

    拓展影响力 2005-10-17
    引用地址:

    评论

  • 从你这篇文章中我得到的信息是,RClass->iv_tbl中保存的是类的实例变量,而

    generic_iv_tbl中保存的才是真正对象的实例变量,这也就是你说的‘二级结构’

    的意思吧。

    但最近看RHG及ruby1.8.5的源代码,感觉你这个说法不正确,因为一般对象的实例变量

    实际是保存在RObject->iv_tbl中的,generic_iv_tbl中保存的应该仅是一些build_in对象的

    实例变量,比如RString实例变量,因为RString结构没有iv_tbl成员,无法保存实例变量。

    虽然你在update中更正了你的说法,但我认为还是没有说清楚。为了不让文章训导其它读者,我

    觉得你还是有必要再修改一下原文。



    最后向你表示感谢,让我们能分享你的想法。谢谢!