浏览 2251 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2010-08-20
最后修改:2010-08-20
上一篇文章我们找到了如何调试Ruby的入口,只要走进去,我们就有可能揭开Ruby的奥秘.但如果我说我要从每个分支都走一遍,每个函数都解读一遍,这可是impossible mission,我肯定没那么强大的理解力,要知道,在没有充分理解一个Ruby对像的实现之前就去阅读它的源码,那大部分的理解都是靠猜测,成功的几率不大,看你的运气以及能得到多少资料.
用过Ruby的人都该知道,Ruby里面没有数据类型的概念:Type是模糊的,但Value是绝对的;123可以是Fixnum,也可以马上变成Bignum,但123就是123,它的值是不变的.
所以当我们阅读R
uby源码的时候,我们看到满眼的声名为VALUE类型的变量,这其实就是Ruby的对象,它是一个指针,指向一块内存,内存里面的数据是值,是结构体,如果你认为它是Fixnum类型(T_FIXNUM),那么你就用FIXNUM_P这个宏去检测一下,如果返回Qtrue,那么恭喜,你的运气很好,一次就碰对了,同时,你马上可以用FIX2INT() or FIX2LONG()这两个宏把它的值在gdb里面打印出来(print或者p);如果返回的不是Qtrue,那么很可惜,你必须继续尝试,在R
uby中处理未知数据类型时,通常用如下代码来完成检测过程,而且随着判断类型的增多,代码会更加冗余:
switch (TYPE(obj)) {
case T_FIXNUM:
/* process Fixnum */
break;
case T_STRING:
/* process String */
break;
case T_ARRAY:
/* process Array */
break;
default:
/* raise exception */
rb_raise(rb_eTypeError, "not valid value");
break;
}
上面这个例子说明了些什么呢?可以说明能量守恒的定理么?我觉得是的,动态语言灵活了,你可以不需要指定类型的去使用,写起代码来一大堆的黑魔法让人看得眼花缭乱.但实际上它的实现过程中却为了作了判断类型的操作,同时由于类型的不确定性,我们还不得不为不管多精简的代码花费这些时间,这就是代价,有得亦有失,这不就是能量守恒么?
由于类型的不确定,由于无论是字符串还是数字还是指针,看起来都是VALUE类型,让我们难以猜测,所以我们这次Ruby调试之旅会很艰苦,因为当我们进入一个断点,watch一个变量的时候,我们将无从下手,我们不知道是该以什么类型去print这个VALUE指针.假如我们得到了一个VALUE变量,我们想拿到它的值,比如String的字符,Fixnum的数值,或者Hash的Key,我们都必须去用(类型_P)这个宏去探测,如果你有阅读过并且读懂了源码,那么恭喜你,你可以根据上下文进行猜测,这样命中的几率大的很多.但在没有找到更好的调试方法之前,让我们多干点好事吧(积德).
ps:rb_type这个函数可以帮我们做一些简单判断:
static inline int
rb_type(VALUE obj)
{
if (IMMEDIATE_P(obj)) {
if (FIXNUM_P(obj)) return T_FIXNUM;
if (obj == Qtrue) return T_TRUE;
if (SYMBOL_P(obj)) return T_SYMBOL;
if (obj == Qundef) return T_UNDEF;
}
else if (!RTEST(obj)) {
if (obj == Qnil) return T_NIL;
if (obj == Qfalse) return T_FALSE;
}
return BUILTIN_TYPE(obj);
}
我不该说这么多调试的痛苦,但这些废话还是有点价值的,比如就引出的Ruby在C中的数据结构.如果专门说这个数据结构,我想这一篇肯定说不完了,但我这一篇是想介绍Ruby在main方法中几个初始化过程的,所以我简单的说一下R
uby的几个重要的数据结构.
Ruby的数据都是以VALUE类型存放的,VALUE的定义如下:
typedef unsigned long VALUE;
VALUE类型如此简单,这就是一个指针么.也就是说,在Ruby中,数据都是以指针方式来访问的(除了Fixnum),当我访问的时候,我通过类型转换将这个指针变成我期望的类型,比如我拿到了一个指针h并且我知道它的类型是Hash,那么h所指向的那片内存区域肯定存放了一个Hash对象,我现在要操作这个对象该怎么办?通过如下的宏来实现:
RHASH(h)->hash_method
#define RHASH(obj) (R_CAST(RHash)(obj))
R_CAST,顾名思义,将obj转换为RHash类型,它的实现如下:
#define R_CAST(st) (struct st*)
呵呵,很容易就能看懂吧,Ruby的实现中使用了大量的宏来简化操作,同时又能给出平易近人的命名方式,这使得我们阅读的时候顺畅了不少,感谢Core Team,不过也别忘了上一篇我为什么要强调编译Ruby时的那几个参数了,如果不加上,我们是无法在gdb的时候去通过
GDB 写道
info macro RHASH
来看到RHASH的实现的.
上面是以一个Hash对象的操作为例,讲解R uby对象的存在方式(VALUE指针),以及习惯的操作方法(R_CAST转换).不过刚才也提到过Fixnum不是指针,为啥?如果一个VALUE的类型你能确定是Fixnum的话,就不需要去通过*取值了,我们可以直接拿到Fixnum的指,因为VALUE这个unsign long已经足够大了,它的长度足够存储一个Fixnum,所以Fixnum是以值而不是指针的形式存在的. 这也就说道Ruby中哪些类型是传值的,哪些类型是传址的.如果死记硬背那些教条,我估计肯定没有自己去发现Ruby的底层实现给你的印象更深刻.
下面这几个类型也很重要,尤其是True和False. RUBY_Qfalse = 0,
RUBY_Qtrue = 2,
RUBY_Qnil = 4,
RUBY_Qundef = 6,
上面是这几个类型的值,千万不要小看这几个值阿,他们可不是随便写写的,他们的用处可巧妙了,比如给你看一个宏的实现,
#define RTEST(v) (((VALUE)(v) & ~Qnil) != 0)
这个宏是用作判断一个VALUE对象是否为True或者为False和Nil的,这个判断就用到了上面的几个值的特点,为了让Ansi C的
if表达式在False和Nil的时候都走失败的分支,而只有True的时候走成功的分支.至于怎么实现,请用位运算计算一下,看看是不是取值设计的巧妙.
#define Qfalse ((VALUE)RUBY_Qfalse)
#define Qtrue ((VALUE)RUBY_Qtrue)
#define Qnil ((VALUE)RUBY_Qnil)
#define Qundef ((VALUE)RUBY_Qundef) /* undefined value for placeholder */
Ruby的数据类型远远没有讲清楚呢,那些各种Ruby层对象所对应的C层的结构体,那些Hash存储策略的设计及Hash算法的选择,RString结构变长存储的设计思路和成员aux的作用等,这些我几乎都没有提到过呢.但这不会影响我们继续探索下去,相信我,知道上面那些就足够我们迈步向前了,起码我自己还什么都不懂呢,所以你们别担心.......
好晚了,写着写着,不知不觉就半夜了,明天还要早起,我发现我离原定要写的内容还有好大一截,反倒是想给大家普及的一些基本知识说了好多,剩下本来要讲Ruby解释器初始化的部分,都准备好了一些代码,但我困了,擦,再不睡觉明天要误事了.我就放到下一章吧.好像一口气读完R
uby的实现,但那样不会理解它的精髓,我们要取其精华去其糟粕,慢慢来啊.
============================================================
下面是准备好了的第三篇的部分东西,先放到这里,大家预习下.
/**********************************************************************
main.c -
$Author: akr $
created at: Fri Aug 19 13:19:58 JST 1994
Copyright (C) 1993-2007 Yukihiro Matsumoto
**********************************************************************/
#undef RUBY_EXPORT
#include "ruby.h"
#include "debug.h"
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
RUBY_GLOBAL_SETUP
int
main(int argc, char **argv)
{
#ifdef RUBY_DEBUG_ENV
ruby_set_debug_option(getenv("RUBY_DEBUG"));
#endif
#ifdef HAVE_LOCALE_H
setlocale(LC_CTYPE, "");
#endif
ruby_sysinit(&argc, &argv);
{
RUBY_INIT_STACK;
ruby_init();
return ruby_run_node(ruby_options(argc, argv));
}
}
#define CALL(n) {void Init_##n(void); Init_##n();}
void
rb_call_inits(void)
{
CALL(RandomSeed);
CALL(sym);
CALL(var_tables);
CALL(Object);
CALL(top_self);
CALL(Encoding);
CALL(Comparable);
CALL(Enumerable);
CALL(String);
/*..........*/
}
README.EXT 写道
4. Example - Creating dbm extension
...... (3) write C code. ...... Ruby will execute the initializing function named ``Init_LIBRARY'' in the library. For example, ``Init_dbm()'' will be executed when loading the library. Here's the example of an initializing function. -- void Init_dbm(void) { 省略 } -- 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2010-08-23
就算是静态类型语言,往往也有运行期类型检查 ……
VALUE 是一种 tagged pointer,tagged pointer 的应用广泛的 …… 连 C 语言的指针都可以看作一种 tagged pointer,其 0(NULL) 值就不是指针量。 Ruby 里面调用方法之前只对是否为直接量作检查(IMMEDIATE_P;见 ruby.h 里面 rb_class_of 的定义),这个基本不算速度的瓶颈 …… |
|
返回顶楼 | |