• 2006-11-02

    管窥Ruby——Allocator

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

    Allocator,熟悉编程的人一看到这个名字,自然就会把它与内存分配联系起来,再近一步,它通常是与对象初始化联系到一起。在《对象的生命》中,我讨论过对象初始化的过程,将它分为“分配内存”和“初始化对象”两个动作,Allocator主要就是承担分配内存的工作。

    如果以Java之类可以合分配与初始化于一体的语言来实现Ruby,那是不是意味着可以将Allocator并入到初始化过程中去,从而将它从Ruby中消灭呢?我一开始确实这样想,但无情的现实总会恰如其分的站出来教育人。

    与Ruby源码打交道,有一点需要谨记,我们面对的是两个层次的东西,一个是Ruby层次,一个是C层次。比如,在之前探讨过的关于Class的内容,属于定义在C层次上,而在Ruby代码中写的类都是定义在Ruby层次上。初读代码时,因为把二者混淆,所以,很长一段时间内,我都被它们之间恼人的关系说困扰。实际上,它们之间有如角色和演员,我们在“台上”看到的对象——Ruby对象,后面有一个真正的“表演者”——C对象。

    如何将C层次的对象和Ruby层次的对象结合起来呢?

    我们来看一下如何创建一个对象。我们知道,Ruby中也有“一切皆对象”的口号,类也不例外,所以在Ruby代码中创建对象,不像很多语言中那样有一个专门的new操作符,而是调用了类对象的一个new方法:
        MyClass.new

    这个new方法在源码中是这样定义的:
    rb_define_method(rb_cClass, "new", -1);
    (object.c)

    主要的内容实现在rb_class_new_instance:
    VALUE
    rb_class_new_instance(argc, argv, klass)
        int argc;
        VALUE *argv;
        VALUE klass;
    {
        VALUE obj;

        obj = rb_obj_alloc(klass);
        rb_obj_call_init(obj, argc, argv);

        return obj;
    }
    (object.c)

    在这个对象的方法中,我们可以清楚看见,“分配内存”(rb_obj_alloc)和“初始化对象”(rb_obj_call_init)两个过程。可想而知,rb_obj_call_init完成的是调用Ruby中的对象初始化方法——“initialize”,这里不再赘述。我们关注一下rb_obj_alloc。

    这个方法实际上就是调用前面所提到的Allocator,由它完成对象的内存分配。如果不定义自己的Allocator,那么按照对象继承的关系,我们用到就是超类的方法,在Ruby中,最底层的Allocator定义在Object类中:
    rb_define_alloc_func(rb_cObject, rb_class_allocate_instance);
    (object.c)

    其中rb_class_allocate_instance的实现如下:
    static VALUE
    rb_class_allocate_instance(klass)
        VALUE klass;
    {
        NEWOBJ(obj, struct RObject);
        OBJSETUP(obj, klass, T_OBJECT);
        return (VALUE)obj;
    }
    (object.c)

    NEWOBJ是一个内存分配的过程,OBJSETUP设置了这个对象的几个字段。

    一般在Ruby层次上定义的类都不会有自己的Allocator,这段代码告诉我们,对于这样类的对象,在底层对应的就是一个RObject的结构,不同类对象之间的差异仅仅是klass字段。这也是我在《管窥Ruby——类的变量》中解释rb_ivar_set中真正的主干是T_OBJECT的原因。

    其实,至此所有的故事都可以结束。但是,Ruby中还有许多定义在C层次上的类,找个堂皇点的理由,使用C层次上的类可以比这种用法效率上稍微有些优势,因为RObject的所有实例变量都放到st_table中,显然这种方式没有直接访问来得快:
    struct RObject {
        struct RBasic basic;
        struct st_table *iv_tbl;
    };
    (ruby.h)

    既然有定义C层次类的需求,那怎么才能把它加入到Ruby的体系之中呢?回头看看rb_class_new_instance,实际上,rb_obj_alloc和rb_obj_call_init这两个函数除了承担前面所说的工作之外,还有其它的角色,按照前面的讨论,要说的已经很清楚了,rb_obj_alloc用来初始化C层次的类,rb_obj_call_init用来初始化Ruby层次的类。

    如果需要加入C层次的类,我们要做的就是加入一个Allocator,Ruby提供了这样一个函数:
    void rb_define_alloc_func( VALUE classmod, VALUE(*func)() );

    这里classmod表示一个Ruby层次上的类,而Allocator中定义一个C层次上的类,通过这段函数,我们就将它们关联了起来:

    下面是一个例子,来自Array的实现:
    rb_define_alloc_func(rb_cArray, ary_alloc);
    (array.c)

    ary_alloc的实现如下:
    static VALUE
    ary_alloc(klass)
        VALUE klass;
    {
        NEWOBJ(ary, struct RArray);
        OBJSETUP(ary, klass, T_ARRAY);

        ary->len = 0;
        ary->ptr = 0;
        ary->aux.capa = 0;

        return (VALUE)ary;
    }
    (array.c)

    回到最初的问题上,想让Allocator退出历史舞台还真不是一件容易的事情,因为它在分配内存之外,还扮演了初始化底层类的角色。如果采用相同的结构,即便用Java这样的语言可以避开内存分配,但底层类初始化的过程是无法避开的。当然,在这种情况下,还叫Allocator,名称上会有些歧义。

    分享到:

    历史上的今天:

    Moco 0.9发布 2013-11-02
    引用地址:

    评论

  • 你的Ruby系列写的益发深入了,请再接再厉。
    回复Mountain说:
    多谢你的鼓励!
    2006-11-04 11:01:44