##第二章 對象
###對象
####點睛
本章開始我們終于要開始探索ruby的源碼了。首先按照預告我們先從對象的構造開始。
接下來我們來考慮一下對象作為對象而成立的必要條件吧。
之前已經說明過幾次對象到底是什么,其實作為對象不可缺少的條件有三個,分別是
* 可以區別自己和外界。(擁有識別標志)
* 能夠對外界的行動作出反應。(方法)
* 擁有內部狀態。(實例變量)
本章會對這三個特征順序確認。
主要注目的文件是ruby.h,其他諸如object.c, class.c, variable.c也會稍微關注。
####value和對象的結構體
ruby中對象的實體是由結構體來表現的。各種處理經常要和指針打交道。
結構體根據不同的類會使用不同的類型,但是指針無論是在哪個結構體中
都是VALUE類型。
[](img/ch_object_value.jpg)
[](img/ch_object_value.jpg)
VALUE的定義如下。
```
71 typedef unsigned long VALUE;
(ruby.h)
```
VALUE實際上被用來強制轉換成各種對象結構體的指針。所以當指針的長度和unsigned long不一致的時候ruby就無法正常工作。嚴格意義上來說,如果存在比sizeof(unsigned long)更長的指針類型ruby會無法正常工作。當然最近的系統基本上都符合上述要求,過去不符合這種要求的機器還是很多的。
至于結構體,也是有很多種類。主要是按照對象的類來進行區別。
struct RObject 凡是不符合以下條件的
struct RClass Class對象
struct RFloat 小數
struct RString 字符串
struct RArray 數組
struct RRegexp 正則表達式
struct RHash 哈希表
struct RFile IO、File、Socket之類
struct RData 上述以外所有用C語言定義的類
struct RStruct Ruby的結構體Struct類
struct RBignum 大整數
我們來看幾個對象結構體的定義的例子。
```
/* 用作一般對象的結構體 */
295 struct RObject {
296 struct RBasic basic;
297 struct st_table *iv_tbl;
298 };
/* 字符串的結構體(String實例) */
314 struct RString {
315 struct RBasic basic;
316 long len;
317 char *ptr;
318 union {
319 long capa;
320 VALUE shared;
321 } aux;
322 };
/* 數組(Arrayの實例)的結構體 */
324 struct RArray {
325 struct RBasic basic;
326 long len;
327 union {
328 long capa;
329 VALUE shared;
330 } aux;
331 VALUE *ptr;
332 };
(ruby.h)
```
我們先把逐個詳細的說明放到后面,首先從整體層面上來說。
首先VALUE被定義成unsigned long類型,要作為指針使用就必須進行強制轉換。
于是各個對象的構造函數就配有Rxxxx()這種形式的宏。比如struct RString的宏就是RSTING(),struct RArray的宏是RARRAY()。這些宏的用法如下。
```
VALUE str = ....;
VALUE arr = ....;
RSTRING(str)->len; /* ((struct RString*)str)->len */
RARRAY(arr)->len; /* ((struct RArray*)arr)->len */
```
接下來我們看到所有的對象結構體的開頭都會有一個struct RBasic類型的成員basic存在。結果就是無論VALUE是指向何種對象的結構體的指針,只要被強制轉換成struct RBasic*,就可以訪問basic的內容。
[](img/ch_object_rbasic.jpg)
[](img/ch_object_rbasic.jpg)
既然這么費勁心思作出這種設計,struct RBasic一定存放著Ruby對象的重要信息。我們來看struct RBasic的定義。
```
290 struct RBasic {
291 unsigned long flags;
292 VALUE klass;
293 };
(ruby.h)
```
flags是擁有多種目的的標志,最重要的用途就是保存結構體的類型(struct RObject之類)。表示類型的標志用T_xxxx來定義。可以從VALUE通過宏TYPE()訪問。比如下面的例子
```
VALUE str;
str = rb_str_new(); /* 生成ruby的字符串(對應結構體為RSting) */
TYPE(str) /* 返回值為T_STRING */
```
這些標志都是T_xxxx的形式,struct RString 的話就是T_STRING,struct RArray的話就是T_ARRAY,對應方式十分規則。
struct RBasic的另一個成員klass保存的是對象所屬的類。klass的類型是VALUE,足以說明其保存的是Ruby的對象(其實是對象的指針)。也就是說這個就是class類的對象了。
[](img/ch_object_class.jpg)
[](img/ch_object_class.jpg)
對象和類的關系在本章“方法”這一小節中會詳細說明。
順帶一提成員名用klass代替class是為了防止用C++的編譯器編譯時和保留詞class發生沖突。
####關于結構體的類型
我們說過struct Basic的成員flags保存了結構體的類型。可是為什么必須保存結構體的類型呢?這是因為所有類型的結構體都是通過VALUE來處理的。當指向結構體的指針被轉換成VALUE之后變量中已經不存在類型的信息,編譯器也是不會特殊照顧的。于是我們只有自己管理好各自的類型了。這個也是針對所有結構體類型統一管理的一大缺陷。
那么,既然使用的結構體是由類決定的,那么為什么還要把結構體和類分開保存呢?直接從類中訪問結構體的類型不就好了嗎?不這樣做有兩個理由。
第一,抱歉我們要推翻剛才說過的話。實際上存在著不具有struct RBasic的結構體(即不存在klass成員)。比如說將在第二部分登場的struct RNode。但是這種特殊的結構體開頭還是會有一個flags類型的成員。所以所有結構體只需要擁有flags就可以進行統一管理。
第二,其實類和結構體不是一一對應的。比如說用戶用Ruby語言定義的類的實例全部是使用struct RObject來保存。如果要從類中訪問結構體的類型就必須記錄下所有類和結構體的對應關系。那還不如直接將類的信息放入結構體中來的快捷便利。
basic.flags的用途
談到basic.flags的用途,剛才一直說是保存結構體類型,這種說法有點惡心我們還是用圖來進行一下說明。此圖僅僅是為了在之后遇到疑惑的時候可以方便參考,現在還不必全部理解。

僅僅是看圖的話我們會發現32比特中還有21比特的空余。其實那些部分被定義為FL_USER0~FL_USER8這一系列的標志,根據結構體不同使用目的也不相同。上圖為了做示范把FL_USER0也放了進去。
###VALUE的填充對象
我們說過VALUE本質上是unsigned long。因為VALUE僅僅是指針,貌似 void*也能勝任,實際上不這樣做是有理由的。因為VALUE也有不是指針的時候。非指針的VALUE存在以下六種情況。
* 數值較小的整數
* 符號(symbol)
* true
* false
* nil
* Qundef
我們按順序來說。
####數值較小的整數
Ruby中一切都是對象所以整數也是對象。但是整數的實例是在是太多了,如果每個整數都用一個結構體來表示的話那運行速度就太慢了。假如要遞加從0到50000的整數,僅僅如此就要生成50000個對象的話會讓人一瞬間陷入猶豫。
那么我們來實際看一下C中獎int類型變換成Fixnum的宏INT2FIX吧,我們來確認一下Fixnum的確是被填埋到了VALUE里面。
```
123 #define INT2FIX(i) ((VALUE)(((long)(i))<<1 | FIXNUM_FLAG))
122 #define FIXNUM_FLAG 0x01
(ruby.h)
```
左移一位之后和1相或。
```
110100001000 變換前
1101000010001 變換后
```
就是如此,保證了保存Fixnum的VALUE總是奇數。另一方面,Ruby對象結構體內存空間的申請使用的是malloc()。一般總是會被分配到4的倍數的地址。所以地址的值和保存Fixnum的VALUE值的范圍是不會重疊的。
另外,將int和long變換成VALUE還有其他一些宏,比如INT2NUM(),lONG2NUM()。它們都是以“○○2○○”的形式,NUM的話可以同時處理Fixnum和Bignum。比如INT2NUM()會把超過Fixnum范圍的數轉換成Bignum。NUM2INT()會把Fixnum和Bignum都轉換成int類型。如果超出int的范圍會發生例外,沒有必要在這里進行越界檢測。
####符號(symbol)
符號是什么?
這個問題比較麻煩,我們先來說為什么需要符號。首先我們知道ruby內部存在著ID類型的變量。
```
72 typedef unsigned long ID;
(ruby.h)
```
這個ID是和任意的字符串一一對應的整數。雖然話這么說,但是也不可能和這個世界上所有的字符串都一一對應吧?這種對應關系僅僅是存在于"ruby的進程之中"。關于ID的訪問方法我們在下一章"名稱和命名表"中講述。
語言處理的程序需要處理大量的名字。變量名,常量名,類名,文件名等等。這些大量的名字如果用char*來保存處理實在是太不容易了。如果你硬要問是哪里不容易,我可以告訴你那就是除了內存管理還是內存管理。另外名字的比較也經常會發生,如果每次都比較字符串是否相符的話實在是效率太低。于是我們不直接處理字符串,而是將其對應到別的東西上面來處理。而那“別的東西”就是整數了。因為整數的處理是最簡單的。
將ID帶入到ruby的世界中的正是符號。ruby1.4之前直接將ID的值轉換成Fixnum作為符號使用。現在也可以通過Symbol#to_i來訪問它的值。然而在實際運用中逐漸發現把符號當作Fixnum處理實在不太妥當,于是在ruby1.6之后就有了獨立的符號類Symbol了。
符號對象由于經常作為哈希表的鍵使用所以數量非常多。于是Symbol就和Fixnum一樣被填埋進了VALUE里面。我們來看一下將ID轉換成Symbol的宏ID2SYM()。
```
158 #define SYMBOL_FLAG 0x0e
160 #define ID2SYM(x) ((VALUE)(((long)(x))<<8|SYMBOL_FLAG))
(ruby.h)
```
左移8bit就相當于乘上256,也就是4的倍數。然后和0x0e相或(這個時候和加法沒區別),這樣的話最終結果也不會是4的倍數。當然也不會是奇數,也就是說不會和其他的VALUE類型相重疊。真是巧妙的方法。
最后我們來看一下ID2SYM()的逆變化SYM2ID()。
```
161 #define SYM2ID(x) RSHIFT((long)x,8)
(ruby.h)
```
RSHIFT是右移。右移根據平臺不同會出現整數的符號剩余,不剩余的區別,所以保險起見我們用宏來代替。
####true false nil
這三個是Ruby里面特殊的對象。分別是代表邏輯真,邏輯假,和沒有對象的對象。這三者的c語言中的值定義如下。
```
164 #define Qfalse 0 /* Rubyのfalse */
165 #define Qtrue 2 /* Rubyのtrue */
166 #define Qnil 4 /* Rubyのnil */
(ruby.h)
```
這次竟然是偶數了。但是要注意0和2作為指針使用是不可能的。所以不必擔心和其他的VALUE值重復。因為虛擬內存空間第一塊地址通常不會被分配。同時也是為了當想要訪問NULL指針的時候程序能夠迅速得出錯。
另外Qfalse因為值為0所以才c語言層次也是被當作邏輯假來使用的。實際上在ruby的返回邏輯真假值的函數中,返回值會被轉換成VALUE或者init然后返回Qtrue/Qfalse。這種做法很常見。
至于Qnil,有專門針對VALUE判斷其是否為Qnil的宏。NIL_P()
```
170 #define NIL_P(v) ((VALUE)(v) == Qnil)
(ruby.h)
```
~p這種命名方式術語lisp風格。其表示進行的處理是返回真值的行為。也就是說NIL_P其意思"為參數是否為nil?" p來自于predicate。(斷言/謂語)。這種命名規則在ruby中被廣泛使用。
另外ruby中除了false和nil是假其他都是真。但是c中的話nil(Qnil)也是真。于是用c語言判定Ruby表達式真假的宏RTEST()應該如下。
```
169 #define RTEST(v) (((VALUE)(v) & ~Qnil) != 0)
(ruby.h)
```
Qnil只有第三位的比特是1,~P取反之后只有第三位是0。與其bit 和之后結果是真的只有Qfalse和Qnil。
加上!=0是為了確保結果是0或者1。因為glib這個庫要求真值只能是0或者1。([ruby-dev:11049])
說來Qnil的Q是什么玩意兒?是R的話還可以理解,為什么是Q呢?向人詢問的結果是因為emacs中是這樣的。比預料中有趣呢。
#### Qundef
```
167 #define Qundef 6 /* undefined value for placeholder */
(ruby.h)
```
這個值在解釋器內部作為“未定義值”來使用。在Ruby中是不會出現的。
###方法
Ruby對象最重要的性質要數擁有自我身份,能夠調用方法,以及按照實例存有數據這三個方面了。這個小節我們來講第二點,對象和方法結合的方式。
####struct RClass
在ruby中類也是作為對象存在的。那當然類的對象的實體也需要一個結構體。這個結構體就是struct RClass了。這個結構體類型的flag為T_CLASS。
另外類和模塊基本屬于同一概念所以沒有必要區分各自的實體。于是模塊的結構體也是用struct RClass來表現的。模塊的結構體flag被設置成T_MODULE來進行區別。
```
300 struct RClass {
301 struct RBasic basic;
302 struct st_table *iv_tbl;
303 struct st_table *m_tbl;
304 VALUE super;
305 };
(ruby.h)
```
首先注意到m_tbl(Method Table)這個成員。struct st_table是ruby中到處可見的哈希表。詳細會在下一章“名稱與命名表”中說明,總之就當作他是記錄一對一關系的東西就好了。m_tbl就是用來記錄這個類所有的方法的名稱(ID)和方法實體直接對應關系的。關于method實體的構造方法我們會在第二部,第三部進行解說。
接下來第四個成員super正如字面意思,保存著父類的信息。因為是VALUE所以指向的是父類的類的對象(指針)。Ruby中不存在父類的類只有Ojbect。(譯者注:在ruby1.6之前是如此)
實際上Object里面的所有方法都定義在Kernel這個模塊中。Object只是將其引入。這個之前我們也已經講過了。模塊的功能幾乎和多重繼承相當,一見似乎只是用super的話無法表現一些復雜的關系,ruby中正是用了巧妙的方法使其看起來只是單一繼承。這個操作我們會在第四章“類和模塊”中說明。
另外受這個變化的影響Object的結構體的super的內容是Kernel實體的struct Rclass, 后者的super被定義成NULL。換句話說,super如果是NULL的話RClass就是Kernel的實體。
[](img/ch_object_classtree.jpg)
[](img/ch_object_classtree.jpg)
####方法的搜索
既然類呈現這樣的構造方式那么該如何調用方法也可以很容易想象了。首先探索對象類的m_tbl,如果沒有找到就繼續順著super在父類中的m_tbl中尋找,依次回溯。也就是說如果知道Object也沒有找到該方法,那么該方法就是還未定義。
按照以上的順序來探索m_tbl的方法請見search_method()。
```
256 static NODE*
257 search_method(klass, id, origin)
258 VALUE klass, *origin;
259 ID id;
260 {
261 NODE *body;
262
263 if (!klass) return 0;
264 while (!st_lookup(RCLASS(klass)->m_tbl, id, &body)) {
265 klass = RCLASS(klass)->super;
266 if (!klass) return 0;
267 }
268
269 if (origin) *origin = klass;
270 return body;
271 }
(eval.c)
```
這個函數在類對象klass中尋找名稱為id的方法。
`RCLASS(value)`的內容為`((struct RClass*)(value))`的宏。
st_lookup()是用來在st_table中檢索和鍵對應的值的函數。如果找到值就返回真,并將找到的值寫入第三個地址參數(body)。這邊這些函數在卷尾的函數參照中會有記載,如有需要可以隨時參考。
話說來每次都進行探索的話實在是太慢了。實際上被調用的參數會被保存到緩存中,第二次就不必每次都用super方法來回溯尋找了。包括緩存的搜索我們會在第十五章“方法”中進行介紹。
###實例變量
這章節我們介紹成為對象必須條件的第三點,實例變量的實裝。
####rb_ivar_set()
實例變量是一種以對象為單位存放其特有的數據的方式。既然是對象特有那么好像將數據保存到對象內部(對象的結構體)會比較好?那么實際上是個什么情況呢?我們來看一下將實例變量帶入對象中的函數rb_ivar_set()一探究竟。
```
/* 向obj對象的成員變量id中代入val */
984 VALUE
985 rb_ivar_set(obj, id, val)
986 VALUE obj;
987 ID id;
988 VALUE val;
989 {
990 if (!OBJ_TAINTED(obj) && rb_safe_level() >= 4)
991 rb_raise(rb_eSecurityError,
"Insecure: can't modify instance variable");
992 if (OBJ_FROZEN(obj)) rb_error_frozen("object");
993 switch (TYPE(obj)) {
994 case T_OBJECT:
995 case T_CLASS:
996 case T_MODULE:
997 if (!ROBJECT(obj)->iv_tbl)
ROBJECT(obj)->iv_tbl = st_init_numtable();
998 st_insert(ROBJECT(obj)->iv_tbl, id, val);
999 break;
1000 default:
1001 generic_ivar_set(obj, id, val);
1002 break;
1003 }
1004 return val;
1005 }
(variable.c)
```
rb_raise()和rb_error_frozen()都是錯誤檢測。這之后我們會反復強調,錯誤檢測雖然在現實中是需要的,但是不是處理的本質。所以第一次讀代碼的時候可以將錯誤處理完全忽略。
去掉了錯誤處理之后就只剩下了swtich語句。類似
```
switch (TYPE(obj)) {
case T_aaaa:
case T_bbbb:
:
}
```
的形式是ruby特有的書寫習慣。TYPE()返回對象構造體的類型(T_OBJECT和T_STRING之類)的宏。類型flag因為是整數所以完全可以使用switch分支。Fixnum和Symbol雖然不存在構造體,但是會在TYPE()中j進行特殊處理進而返回T_FIXNUM和T_SYMBOL,所以大可不必擔心。
接下來我們回到`rb_ivar_set()`。好像就只有T_OBJECT T_CLASS T_MODULE這三個類型的處理是分開來單獨進行的。這三個被選中是因為他們的結構體的第二個成員是iv_tbl。我們來實際確認一下。
```
/* TYPE(val) == T_OBJECT */
295 struct RObject {
296 struct RBasic basic;
297 struct st_table *iv_tbl;
298 };
/* TYPE(val) == T_CLASS or T_MODULE */
300 struct RClass {
301 struct RBasic basic;
302 struct st_table *iv_tbl;
303 struct st_table *m_tbl;
304 VALUE super;
305 };
(ruby.h)
```
iv_tbl對應的是Instance Variable Table,也就是實例變量表。里面記錄的是實例變量名和對應的值。
我們再來貼一下rb_ivar_set()中,當結構體擁有iv_tbl成員時的處理代碼。
```
if (!ROBJECT(obj)->iv_tbl)
ROBJECT(obj)->iv_tbl = st_init_numtable();
st_insert(ROBJECT(obj)->iv_tbl, id, val);
break;
```
ROBJECT()是用來將VALUE強制轉換成struct RObject*的宏。obj指向的也有可能是struct Rclass,但是僅僅是訪問第二成員變量的話是不會發生什么問題的。
st_init_numtable()是新生成st_table的函數。st_insert()是在st_table中生成關聯的函數。
綜上所述這段代碼所做的事情,就是當iv_table()不存在的時候新建一個,然后將對應的"變量名=>對象"記錄到其中。
注意一點,struct Rclass自身是類對象的結構體,所以其變量表也是類對象自己的東西。用ruby程序來說,就如下面這種情況。
```
class C
@ivar = "content"
end
```
####generic_ivar_set()
向T_OBJECT T_MODULE T_CLASS之外的結構體代入變量會是怎么樣呢?
```
#沒有iv_tbl的結構體
1000 default:
1001 generic_ivar_set(obj, id, val);
1002 break;
(variable.c)
```
這個時候處理會交給generic_ivar_set()。看具體的函數之前先把大框架說明一下吧。
T_OBJECT T_MODULE T_CLASS以外的構造體是沒有iv_tbl成員的(之后會說明為什么沒有的理由)。但是就算是沒有該成員也可以通過別的手段將實例和struct st_table對應起來。ruby將這種對應關系保存在全局的st_table,即generic_iv_table中來解決此問題。
[](img/ch_object_givtable.jpg)
[](img/ch_object_givtable.jpg)
我們來看一下實際的代碼。
```
801 static st_table *generic_iv_tbl;
830 static void
831 generic_ivar_set(obj, id, val)
832 VALUE obj;
833 ID id;
834 VALUE val;
835 {
836 st_table *tbl;
837
/* 總之可以先無視 */
838 if (rb_special_const_p(obj)) {
839 special_generic_ivar = 1;
840 }
/* 不存在generic_iv_tbl則新建 */
841 if (!generic_iv_tbl) {
842 generic_iv_tbl = st_init_numtable();
843 }
844
/* 核心處理 */
845 if (!st_lookup(generic_iv_tbl, obj, &tbl)) {
846 FL_SET(obj, FL_EXIVAR);
847 tbl = st_init_numtable();
848 st_add_direct(generic_iv_tbl, obj, tbl);
849 st_add_direct(tbl, id, val);
850 return;
851 }
852 st_insert(tbl, id, val);
853 }
(variable.c)
```
rb_special_const_p()當參數不是指針的時候返回真。這個if語句里面的內容如果不理解垃圾回收機制的話是無法說明的,所以我們在這里還是先省略。
請讀者在閱讀第五章的垃圾回收機制之后再自行理解。
st_init_numtable()剛才也出現了,是用來新建一個哈希表。
st_lookup()用來查找和key對應的值。這個時候會查找和obj所關聯的實例變量表。如果找到對應的值函數整體就返回真,在第三個地址參數(&tbl)中記錄找到的對應值。也就是說!st_lookup(...)將的是當沒有找到對應記錄之后發生的事情。
st_insert()剛才也說過了。用來將新的對應記錄保存到表中。
st_add_direct()和st_insert()幾乎相同,區別在于后者會在追加保存記錄之前先檢查一下鍵是否已經存在。也就是說如果使用st_add_direct()來添加新記錄的話,如果已經有相同的鍵存在于表中,會出現一個鍵對應兩個記錄的情況。
所以能直接使用st_add_direct()的情況,基本上是剛剛確認過鍵不存在,或者是剛剛建立新的表這兩種情況。這段代碼是符合這些情況的。
FL_SET(obj, FL_EXIVAR)是用來設置obj的basic.flags為FL_EXIVAR的宏。basic.flags的所有flag都是FL_xxx的形式,可以通過FL_SET來設置flag。相反用來取消對應flag的宏叫做FL_UNSET()。另外,通常認為FL_EXIVAR的EXIVAR是external instance variable的簡稱。
插入這個flag是為了提高訪問實例變量的速度。如過發現沒有這只FL_EXIVAR這個falg,則不用通過探索generic_iv_tbl也可以知道實例變量不存在。相比之下當然是bit的校驗比起探索struct st_table來的速度更快。
####結構體的間隙
現在我們了解了實例變量的保存方式,那么為什么存在沒有iv_table的結構體呢?比如struct RString和struct RArray就沒有iv_tbl,這個是為什么?那么干脆直接把實例變量當作RBasic的成員算了。
就結論來說,可以這樣做但是不應該如此。實際上這個和ruby的對象管理機制緊密相關。
ruby中字符串的數據(char[])所占用的內存使用malloc來訪問。但是對象的結構體是個例外。ruby會統一管理分配,從而訪問內存。這個時候如果結構體的種類(大小)參差不齊的話管理起來就十分麻煩。所以將所有的結構體用共用體Rvalue來申明并統一分配管理。共用體的大小會和成員中最大的保持一致,所以如果單獨有一個結構體的體積非常大就會造成很大的浪費,于是我們還是希望素有的結構體的大小盡可能保持接近。
最常用的結構體要數struct RString(字符串)了吧。接下來是RArray(數組),RHash(哈希),RObject(所有用戶定義的類)
。那么問題就來了。struct RObject的內容只有sruct RBASIC和一個指針。而struct RString RHash RArray卻已經使用了struct Basic和三個指針的空間。也就是說struct Robject越多,就會造成越多的兩個指針空間的浪費。更有甚者,RString如果使用了四個指針,RObject實際上就只占用了共用體的一半不到的空間,實在是太浪費了。
另一方面配置iv_tbl的好處主要是訪問速度的提升和內存空間的節省,并且我們也不知道這個功能會不會被頻繁使用。實際上在ruby1.2之前根本就沒有導入過generic_iv_tbl,所以像String和Array中也不能使用實例變量,就算如此也沒發生什么大問題。如果僅僅因為這點好處就浪費大量的空間實在是太蠢了。
所以從以上可以做出結論,因為iv_tbl而增加構造體的體積實在是無奈之舉。
```
960 VALUE
961 rb_ivar_get(obj, id)
962 VALUE obj;
963 ID id;
964 {
965 VALUE val;
966
967 switch (TYPE(obj)) {
/*(A)*/
968 case T_OBJECT:
969 case T_CLASS:
970 case T_MODULE:
971 if (ROBJECT(obj)->iv_tbl &&
st_lookup(ROBJECT(obj)->iv_tbl, id, &val))
972 return val;
973 break;
/*(B)*/
974 default:
975 if (FL_TEST(obj, FL_EXIVAR) || rb_special_const_p(obj))
976 return generic_ivar_get(obj, id);
977 break;
978 }
/*(C)*/
979 rb_warning("instance variable %s not initialized", rb_id2name(id));
980
981 return Qnil;
982 }
(variable.c)
```
結構基本相同。
(A)struct RObject如果是Rclass,則檢索iv_tbl。剛才說過了,有可能iv_tbl是NULL,所以不先檢查一下的話會出錯。接著st_lookup會在找到相應記錄的時候返回真。這個if語句整體來說就是"如果已經代入過此實例變量則返回其值"。
(C)如果沒有找到對應的值……也就是說如果想要訪問的是還沒有被代入的實例變量,那么跳過if和switch,直接運行下面的語句。這個時候會發生rb_warning(),并返回nil。這是因為ruby的實例變量不需要代入也可以被訪問。
(B)另外,當結構體既不是struct RObject也不是RClass的時候,首先從generic_iv_tbl中尋找對象的實例變量表。generic_ivar_get()實現的功能不用我說也能想到。另外需要注意的是if語句。
剛才說過將進行過generic_ivar_set()處理的對象插入FL_EXIVAR。在這里這個flag使得運行高速化的特征就顯現出來了。
rb_special_const_p()是什么玩意兒?這個函數當obj不存在結構體的時候為真。因為不存在構造體所以也不需要basic.flags(因為根本無從插入flag)。所以FL_xxx()遇到這種對象總是會返回假。于是這里對待rb_special_const_p()為真的對象必須格外小心翼翼。
###對象的結構體
這小節中我們簡要介紹對象結構體最重要的具體內容的的處理方法。
####struct RString
```
314 struct RString {
315 struct RBasic basic;
316 long len;
317 char *ptr;
318 union {
319 long capa;
320 VALUE shared;
321 } aux;
322 };
(ruby.h)
```
ptr是指向字符串的指針,len是字符串的長度,非常直觀。
Ruby的字符串與其說是字符串不如說是字符序列。因為可以包含NUL在內的任何字符。所以在ruby中就算在終端設置NUL也沒有多大意義,不過因為C的函數中要求NUL,所以為了便利還是將NUL設置成字符串的終端。但是NUL是不包含在len之中的。
另外解釋器和擴展庫中處理字符串的時候可以通過`RSRTING(str)->ptr``RSTRING(str)`來訪問ptr和len。但是要注意以下幾點。
* 檢查str是否確實指向struct RString。
* 可以訪問成員但是不能改變其內容
* 不可以將RSTRING(str)->ptr保存到諸如臨時變量之中供之后使用。
這是為什么?其中一個原因是軟件工程學上的原則。不可以隨便修改別人的數據。既然有接口函數就乖乖使用接口函數。不過還有其他不允許擅自訪問和保存指針的理由,這和第四個成員變量aux有關。但是要詳細說明aux的使用方法又必須得詳細說明ruby字符串的一些特征。
ruby的字符串自身是可以改變的(mutable),所謂的變化是指
```
s = "str" # 生成字符串代入s
s.concat("ing") # 向字符串s中追加"ing"。
p(s) # 輸出"string"
```
這樣s指向的對象的內容就變成了"string"。java和Python的字符串是沒有這種特性的。硬要說的話,這個特性和Java的StringBuffer很接近。
接下來我們來看看他們之間到底有什么關系。首先既然可以發生改變自然字符串的長度len也會改變。既然長度發生改變那么這個時候內存的分配就會發生增減。當然也可以使用realloc(),但是malloc和realloc這些操作都太重了,僅僅是為了變更字符串就realloc()一次的話實在是負擔太大了。
于是ptr所指向的內存空間通常會比len稍微長一點。這樣的話追加的字符串正好能放入多余的內存中就可以不必調用realloc()了,這樣的話速度就上去了。結構體中的aux.capa保存的就是這個多余的長度。
那么另一個aux.shared是什么玩意兒呢。這個也是為了提高從字符串序列中生成對象的速度而采用的機制。請看下面的ruby程序。
```
while true do # 永遠地重復
a = "str" # 將內容是"str"的字符串放入a
a.concat("ing") # 向a中追加"ing"。
p(a) # 輸出string
end
```
不管是循環多少次總會在第四行的p出輸出"string"。放在一般情況下那就得每次通過"str"這個式子新建一個char[]類型的字符串對象。
但是對于字符串通常情況下都不會做任何改變,這個時候就會造成多次復制char[]的資源浪費。于是我們就期望能夠共用一個char[]。
作為共用所存在的就是aux.shared這個東西了。使用表達式生成的字符串對象都會共用同一個char[]。只有當真正發生變化時候才會專門去申請內存分配。使用共用的char[]的結構體中的basic.flags標志中會被設立ELTS_SHARED這個標志。aux.shared會保存原來的對象。ELTS是elements的簡稱。
我們回到RSTRING(str)->ptr的話題中。之所以可以訪問但不能改變指針對象是因為這會使得len和capa的值和真實情況不符。另外如果要改變用序列表達式所新建的字符串對象的內容,則需要把對象中的aux.shared成員移除。
最后我們來列舉幾個使用RString的例子。str可以看成是指向RString的value。
```
RSTRING(str)->len; /* 長度 */
RSTRING(str)->ptr[0]; /* 第一個字符 */
str = rb_str_new("content", 7); /* 生成內容是"content"的字符串。
第二個參數是其長度 */
str = rb_str_new2("content"); /* 生成內容是"content"的字符串。
長度會使用strlen()來計算 */
rb_str_cat2(str, "end"); /* 在Ruby字符串中后接C的字符串 */
```
###struct RArray
struct RArray是存放Ruby的數組實例的結構體。
```
324 struct RArray {
325 struct RBasic basic;
326 long len;
327 union {
328 long capa;
329 VALUE shared;
330 } aux;
331 VALUE *ptr;
332 };
(ruby.h)
```
除了ptr之外幾乎和struct RString一樣。ptr指向的是數組的內容,len是其長度。aux的用法和struct RString中介紹的相同。aux.capa是ptr所指向的內存的真正的長度,aux.shared則是當數組為共用的時候指向共用數組的指針。
訪問成員的方法也和RString類似。 通過RARRAY(arr)->ptr和RARRAY(arr)->len可以訪問成員但是不能改變成員的內容。我們來看一下簡單的例子。
```
/* 用c語言操作數組 */
VALUE ary;
ary = rb_ary_new(); /* 生成空的數組 */
rb_ary_push(ary, INT2FIX(9)); /* 將Ruby的9加入數組 */
RARRAY(ary)->ptr[0]; /* 訪問編號是0的元素 */
rb_p(RARRAY(ary)->ptr[0]); /* 輸出ary[0](輸出9) */
# 用ruby進行操作
ary = [] # 生成空的數組
ary.push(9) # 將Ruby的9加入數組
ary[0] # 訪問編號是0的元素
p(ary[0]) # 輸出ary[0](輸出9)
```
###struct RRegexp
RRepexp是存放正則表達式的結構體。
```
334 struct RRegexp {
335 struct RBasic basic;
336 struct re_pattern_buffer *ptr;
337 long len;
338 char *str;
339 };
(ruby.h)
```
ptr是已經編譯好的正則表達式。str是編譯前的正則表達式(正則表達式的源碼),len是其長度。
處理Rexgexp對象的代碼本書中將不會出現所以這里就省略了。就算要在擴展庫中使用,只要不涉及很特殊的用法,參考一些接口函數就應該足夠了吧。
###struct RHash
struct RHash是哈希表Hash實例所在的結構體。
```
341 struct RHash {
342 struct RBasic basic;
343 struct st_table *tbl;
344 int iter_lev;
345 VALUE ifnone;
346 };
(ruby.h)
```
其實這個是struct st_table的wrapper。關于st_table我們會在下一章"名稱和命名表"中詳細解說。
ifnone存放的是搜索失敗時候使用的鍵,默認是nil。iter_lev是為了哈希表的re-entrance(多進程安全)存在的。
###struct RFile
struct RFile是服務嵌入類Io和其后繼子類實例的構造體。
```
348 struct RFile {
349 struct RBasic basic;
350 struct OpenFile *fptr;
351 };
(ruby.h)
```
```
19 typedef struct OpenFile {
20 FILE *f; /* stdio ptr for read/write */
21 FILE *f2; /* additional ptr for rw pipes */
22 int mode; /* mode flags */
23 int pid; /* child's pid (for pipes) */
24 int lineno; /* number of lines read */
25 char *path; /* pathname for file */
26 void (*finalize) _((struct OpenFile*)); /* finalize proc */
27 } OpenFile;
(rubyio.h)
```
成員幾乎都保存在了struct OpenFile中。IO對象的實例并不多所以可以這樣存放。各個成員的用途都有些。基本上是C語言stdio的warpper。
###struct RData
struct RData和目前為止介紹的東西目的都不一樣。這個主要是為了存放擴展類的結構體。
編寫擴展庫的類的實體當然也需要一個結構體來存放。但是結構體的類型是生成的類所決定的,所以無法事先知道類的大小和結構。于是ruby提供了一個"管理用戶自定義的結構體指針的結構體",這個東西就是struct RData了。
```
353 struct RData {
354 struct RBasic basic;
355 void (*dmark) _((void*));
356 void (*dfree) _((void*));
357 void *data;
358 };
(ruby.h)
```
data是指向用戶定義的構造體的指針。dfree是釋放自定義構造體的函數,dmark是mark and sweep中進行mark的函數(涉及垃圾回收)
關于struct RDdata的說明現在還不是時候,總之先看圖吧。詳細的內容我們會在第五章的"垃圾回收"中介紹。
[](img/ch_object_rdata.jpg)
[](img/ch_object_rdata.jpg)
(第二章完)