论坛首页 编程语言技术论坛

从main.c开始走进Ruby-有形亦无形的数据

浏览 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)
{
省略
}
--
   发表时间:2010-08-23  
就算是静态类型语言,往往也有运行期类型检查 ……

VALUE 是一种 tagged pointer,tagged pointer 的应用广泛的 …… 连 C 语言的指针都可以看作一种 tagged pointer,其 0(NULL) 值就不是指针量。

Ruby 里面调用方法之前只对是否为直接量作检查(IMMEDIATE_P;见 ruby.h 里面 rb_class_of 的定义),这个基本不算速度的瓶颈 ……
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics