|
VALUE是Ruby的C实现中用来"指向/引用/代表"所有Ruby对象的"指针"的统一的类型

通过统一类型,可以获得类似"多态"的好处,ruby实现起来就更为简洁(代码方面).
然后, 同样是出于实现的考虑(性能方面), VALUE并不全是指针:
1. 小的整数
2. 符号
3. true
4. false
5. nil
6. Qundef
以上六位就是所谓的"内嵌对象"(VALUE并不是内存地址,其代表的对象是内嵌在VALUE的数值中).
小整数的内嵌规则是:
#define INT2FIX(i) ((VALUE)(((long)(i))<<1 | FIXNUM_FLAG)) #define FIXNUM_FLAG 0x01
数值乘以2再加1(奇数).
符号的内嵌规则是:
#define SYMBOL_FLAG 0x0e #define ID2SYM(x) ((VALUE)(((long)(x))<<8|SYMBOL_FLAG))
数值乘以256再加14(不能被4整除的偶数)
看到这里,大家可能会疑惑,小整数的数值是本身,但符号(Symbol)的数值又是什么呢?
简单来说,每个ruby进程中维护了一个字符串和数值一一对应的hash表,
因此就可以使用数值来代表(Identify)字符串所表示的"名字", 减少了内存和字符串比较的开销.
static NODE* search_method(klass, id, origin) VALUE klass, *origin; ID id; { NODE *body; if (!klass) return 0; while (!st_lookup(RCLASS(klass)->m_tbl, id, &body)) { klass = RCLASS(klass)->super; if (!klass) return 0; } if (origin) *origin = klass; return body; }
例如这个search_method就是在klass类中搜索名称的ID为id的方法, 并将方法的"出处"(定义方法的类)保存在orgin中.
Ruby为"数值"的"特殊使用"(用做'名字'的ID)单独创建了一个类型:符号(Symbol).
3-6的内嵌规则是:
#define Qfalse 0 /* Ruby's false */ #define Qtrue 2 /* Ruby's true */ #define Qnil 4 /* Ruby's nil */ #define Qundef 6 /* undefined value for placeholder */
除了内嵌对象外, VALUE的值便是Ruby对象对应的C结构体的内存地址.
C结构体的内存通常不是从第一个块(0-?)开始分配的,因此不会和Qfalse,Qnil冲突.
且所有Ruby对象的C结构体的共同特征是,其内存地址一定是4的倍数(还记得C中Struct对齐么?).
因此,仅凭VALUE的数值就可以区分它是小整数,符号,Q值,还是内存地址(有例外!).
switch (TYPE(obj)) { /* (A) */ case T_OBJECT: case T_CLASS: case T_MODULE: if (ROBJECT(obj)->iv_tbl && st_lookup(ROBJECT(obj)->iv_tbl, id, &val)) return val; break; /* (B) */ default: if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj)) return generic_ivar_get(obj, id); break; }
看到代码中的TYPE宏么, 就是根据VALUE的数值返回obj可能指向的结构体的类型.
例如,一个RString类型的结构体实例的TYPE就是T_STRING. 那么TYPE(2)会返回T_FIXNUM么?
错! 应返回T_TRUE?(如果有...), 而TYPE(3)则返回T_FIXNUM.
TYPE宏可以区分内嵌对象和对象,那么,它是如何区分不同的对象的呢?此时,VALUE的值都是内存地址, 不同的是它所指向的结构体.正如你想的那样, TYPE宏在运行时区分结构体类型的能力正是来源于结构体本身,换句话说,结构体存储了自身的类型信息.关于这些结构体,详见 Ruby的对象类. |
|