最近开始学习 Ruby,现在整理一下自己学习的笔记,针对的 Ruby 是 MRI 版。其中难免有错误之处,还望大家批评指正。
在源代码中可以看到 Ruby 使用 RObject 结构体来存储自定义类对象以及 Ruby 内部创建的少数自定义类,代码如下:
struct RBasic {
VALUE flags;
const VALUE klass;
}
#define ROBJECT_EMBED_LEN_MAX 3
struct RObject {
struct RBasic basic;
union {
struct {
long numiv;
VALUE *ivptr;
struct st_table *iv_index_tbl;
} heap;
VALUE ary[ROBJECT_EMBED_LEN_MAX];
} as;
};
其中各部分的含义如下:
numiv: 实例变量的数目;
*ivptr: 实例变量值数组的指针,这里只保存值;
*iv_index_tbl: 指向一个散列表,该散列表是实例变量名及其在 ivptr 数组中位置的映射,这只是一个缓存,真实的散列是在对象对应的类的 RClass 结构体中的。
ary: 从代码中可以看出,ary 和上述三个变量使用同一块内存空间,它被用来保存实例变量。如果属性个数小于 ROBJECT_EMBED_LEN_MAX 属性值将直接存储在 ary 数组内,如果变量的大小合适,就会被全部保存,这样就不需要为了保存实例变量的值的数组而调用 malloc 来分配额外的内存;否则属性值和索引都存储在 heap 结构体中。
用一个图来表示如下图所示 (假设 ROBJECT_EMBED_LEN_MAX=1):
当我们使用 obj = ClassName.new 来创建一个对象的时候,obj 中会存储属于自己的实例变量的信息,同一个类的不同对象可能拥有不同个数的实例变量。同时 obj 的 klass 会指向自己的类。也就是说:
在 Ruby 中一切皆对象,基本的数据类型也是一样的。Ruby 使用了别的结构体来保存每个基本数据类型的值。如使用 RString 结构体保存字符串的值,使用 RArray 结构体保存数组等。但是,这些不同的结构体中都包含同样的 RBasic 结构体。如下图所示的是 RString 的简化版:
在 RString、RArray、RStruct 和 RBignum 结构体中同样使用了 ary 数组来优化内存。
此外,为了优化性能,Ruby 中保存小值整数、符号和其他一些简单立即值时没有使用任何结构体,只是将值放在 VALUE 中。在源码中,VALUE 的定义如下:
typedef unsigned long VALUE;
VALUE 可以看作是 long 类型的一个别名。在这里,Ruby 将其分为两部分:值和标志位。
在这里 VALUE 不在是指针,它们就是值本身。前几个比特存储值,后边的就是标志位。全部小值整数都有 FIXNUM_FLAG 位标记。
一旦 FIXNUM_FLAG 被设置,Ruby 就知道这个 VALUE 是一个小值整数,而不是指向结构体的指针。
在 Ruby 中,使用这种方式实现的类型还有 Symbol,true,false,nil 和 undef。从中可以看出为什么字符占用的资源比符号要多。
参考:
《Ruby 原理剖析》
xingpingz Ruby 2.X 源代码学习:对象模型