從main.c開始走進Ruby-有形亦無形的數據

 

上一篇文章我們找到了如何調試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)
{
省略
}
--
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章