-
2006-10-17
管窥Ruby——类的变量
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://dreamhead.blogbus.com/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中,类也是对象,类的变量实际上就是类对象的实例变量。
引用地址:










评论
generic_iv_tbl中保存的才是真正对象的实例变量,这也就是你说的‘二级结构’
的意思吧。
但最近看RHG及ruby1.8.5的源代码,感觉你这个说法不正确,因为一般对象的实例变量
实际是保存在RObject->iv_tbl中的,generic_iv_tbl中保存的应该仅是一些build_in对象的
实例变量,比如RString实例变量,因为RString结构没有iv_tbl成员,无法保存实例变量。
虽然你在update中更正了你的说法,但我认为还是没有说清楚。为了不让文章训导其它读者,我
觉得你还是有必要再修改一下原文。
最后向你表示感谢,让我们能分享你的想法。谢谢!