Lua 中字符串管理是核心內容之一(另外一個固然就是表的管理)。
Lua 腳本中用到的字符串,解析時用到的符號,及一些運行時相關的字符串都保存在全局字符串表中,全局字符串表就是 tree.c 中的 string_root 數組。
一樣的字符串在 Lua 的全局字符串表中只會出現一次,也就是隻保存一次。
先看下數據結構 TaggedString
數組
typedef struct TaggedString { Word varindex; /* != NOT_USED if this is a symbol */ Word constindex; /* != NOT_USED if this is a constant */ unsigned long hash; /* 0 if not initialized */ int marked; /* for garbage collection; never collect (nor change) if > 1 */ char str[1]; /* \0 byte already reserved */ } TaggedString;
解釋下各字段:
varindex : 字符串在符號表中的下標,若是字符串是用作符號的話。
constindex : 若是字符串是字符串常量,該字段表示字符串在常量字符串表中的下標。
hash : 字符串的散列值,沒有初始化以前該值爲 0 。
marked : 垃圾回收 gc 的標誌位,若是不須要垃圾回收,應該給它設置一個大於 1 的值。
str[] : 實際字符串,注意這裏已經有一個字符串位置了。
最後一個字符串數組的用法在 C 語言裏叫作和柔性數組,也就是數組名字自己只起到佔位做用。
實際使用的時候,會給結構體 TaggedString 分配一大塊內存,內存的前部由結構體所用,其它的 sizeof(TaggedString) 大小以外的內存,都至關於分配給結構體最後一個數組了,那片內存經過數組名使用。
再看下 stringtable
數據結構
typedef struct { int size; int nuse; /* number of elements (including EMPTYs) */ TaggedString **hash; } stringtable;
各字段的意思:
size : 表的大小,會隨着表項的增長而增長。
nuse : 已經使用的元素個數,包括已經回收的(就是指向一個空元素 EMPTY 的)。
hash : TaggedString 指針的指針,使用時是用作 TaggedString 指針的數組。
再看下全局的字符串表 string_root
static stringtable string_root[NUM_HASHS];
爲何 string_root 有 NUM_HASHS(64)項? 由於ASCII碼 是 128 個,A 的 ASCII 碼是 65,ASCII 碼 64 以前的字符基本上是不會出如今第一位的字符,因此用一半恰好。
看下對外接口
函數
TaggedString *lua_createstring (char *str) { return insert(str, &string_root[(unsigned)str[0]%NUM_HASHS]); }
其中調用了 insert 函數, insert 函數的第二個參數是一個 stringtable 指針,這裏根據字符串的首字母選項 stringtable 表項的結果是把首字母相同字符串存到了一個 stringtable 表項中。而後在存到表項中時再次散列,保存到 stringtable 的 hash 中。這可能也就是明明是字符串處理的文件,卻取個名字叫 tree 的緣由吧。由於散列的關係,字符串保存完後可不就像是一棵棵樹麼。不過更嚴格的說,是不分叉的樹,也就是鏈表了。首字母相同的字符串組成一個鏈表,首字母不一樣的在全局字符串表 string_root 的不一樣的 stringtable 表項中。
看下字符串是如何 insert 到 stringtable 中的
this
static TaggedString *insert (char *str, stringtable *tb) { TaggedString *ts; unsigned long h = hash(str); int i; int j = -1; if ((Long)tb->nuse*3 >= (Long)tb->size*2) { if (!initialized) initialize(); grow(tb); } i = h%tb->size; while (tb->hash[i]) { if (tb->hash[i] == &EMPTY) j = i; else if (strcmp(str, tb->hash[i]->str) == 0) return tb->hash[i]; i = (i+1)%tb->size; } /* not found */ lua_pack(); if (j != -1) /* is there an EMPTY space? */ i = j; else tb->nuse++; ts = tb->hash[i] = (TaggedString *)luaI_malloc(sizeof(TaggedString)+strlen(str)); strcpy(ts->str, str); ts->marked = 0; ts->hash = h; ts->varindex = ts->constindex = NOT_USED; return ts; }
程序一開始,先算出字符串的散列值。計算散列值的函數以下:
lua
static unsigned long hash (char *str) { unsigned long h = 0; while (*str) h = ((h<<5)-h)^(unsigned char)*(str++); return h; }
接下來判斷 stringtable 是不是使用超過三分之二,若是是,須要增長 TaggedString 項。
在未初始化時,須要先進行初始化 initialize。
spa
static void initialize (void) { initialized = 1; luaI_addReserved(); luaI_initsymbol(); luaI_initconstant(); }
先設置初始化標籤。
添加保留字 luaI_addReserved,就是以前在詞法分析中看到的那個函數,注意全部保留字的 TaggedString 的 markded 字段值都是一個比較大的數字,以免垃圾回收。
初始化符號 luaI_initsymbol,把一些內置的函數名字添加到字符串中,設置一些全局環境(這個到說解釋器 table 管理裏再說)。
初始化字符串常量 luaI_initconstant, 就是把兩個預字義的字符串添加到全局中字符串表中去。
再看下 stringtable 的如何增長表項的:
debug
static void grow (stringtable *tb) { int newsize = luaI_redimension(tb->size); TaggedString **newhash = newvector(newsize, TaggedString *); int i; for (i=0; i<newsize; i++) newhash[i] = NULL; /* rehash */ tb->nuse = 0; for (i=0; i<tb->size; i++) if (tb->hash[i] != NULL && tb->hash[i] != &EMPTY) { int h = tb->hash[i]->hash%newsize; while (newhash[h]) h = (h+1)%newsize; newhash[h] = tb->hash[i]; tb->nuse++; } luaI_free(tb->hash); tb->size = newsize; tb->hash = newhash; }
經過當前的 size 調用 luaI_redimension 函數獲得一個 newsize,用這個 newsize 來新建一個 TaggedString 指針數組。
luaI_redimension 定義在 hash.c 中
指針
/* hash dimensions values */ static Long dimensions[] = {3L, 5L, 7L, 11L, 23L, 47L, 97L, 197L, 397L, 797L, 1597L, 3203L, 6421L, 12853L, 25717L, 51437L, 102811L, 205619L, 411233L, 822433L, 1644817L, 3289613L, 6579211L, 13158023L, MAX_INT}; int luaI_redimension (int nhash) { int i; for (i=0; dimensions[i]<MAX_INT; i++) { if (dimensions[i] > nhash) return dimensions[i]; } lua_error("table overflow"); return 0; /* to avoid warnings */ }
代碼相對比較直觀,從一個預先定義好的從小到大排列的長整型數組中從前到後取得第一個大於所傳參數的值。
回到 grow 函數。數組分配內存以後,把每一項初始化爲空。
設置當前的已使用元素 nuse 爲 0 。
把原來的不爲空的表項,拷貝到新分配的內存中去。拷貝時是根據原來的 TaggedString 的 hash 值來找其在新分配的 newhash 中的位置。相應的已使用元素 nuse 自加。
釋放老的 stringtable 中的 hash 數組。
設置 stringtable 的 size 和 hash 分別爲新的 newsize 和 newhash。
回到 insert 函數。根據上面計算獲得的散列值 h 和 stringtable 的 size 算出字符串應該在表項中的位置。若是相應的位置中已經有值了,就向後找表項中第一個空的位置。若是字符串已經存在,就直接返回相應的 TaggedString。
設置字符串前,時行一次垃圾回收 lua_pack(這個在分析解釋器的時候再細看)。
若是找到的位置是 NULL,則 stringtable 的已使用元素個數加一(tb->nuse++)。
若是找到的位置是指向 EMPTY 的,則已經使用元素個數不用自加 (就是 if (j!= -1) 處條件爲真時)。
爲 TaggedString 分配內存空間,拷貝字符串到 TaggedString 的 str 中。
設置相應字段。
lua_createstring 分析完了,順便說一下一個和它關係比較密切的函數。就是咱們以前遇到的 luaI_createfixedstring。
luaI_createfixedstring 定義於 table.c 中
調試
TaggedString *luaI_createfixedstring (char *name) { TaggedString *ts = lua_createstring(name); if (!ts->marked) ts->marked = 2; /* avoid GC */ return ts; }
其內部是調用了 lua_createstring,不過在 TaggedString 建成以後,設置了其 marked 值爲 2(大於 1 的值能夠避免垃圾回收)。
這樣,以前的問題列表 TaggedString 相關的就都回答了。
tree.c 中就只有一個對外接口沒有分析了,順便也看一下它吧。
code
/* ** Garbage collection function. ** This function traverse the string list freeing unindexed strings */ Long lua_strcollector (void) { Long counter = 0; int i; for (i=0; i<NUM_HASHS; i++) { stringtable *tb = &string_root[i]; int j; for (j=0; j<tb->size; j++) { TaggedString *t = tb->hash[j]; if (t != NULL && t->marked <= 1) { if (t->marked) t->marked = 0; else { luaI_free(t); tb->hash[j] = &EMPTY; counter++; } } } } return counter; }
這是字符串的垃圾回收,上面提到的垃圾回收 lua_pack 裏最終就會調用到它。這個代碼也比較直觀,就是遍歷全局字符串表,把全部 TaggedString 的 marked 字段爲 0 的進行釋放,同時設置它在 stringtable 中的表項爲空 EMPTY。若是 marked 字段爲一個不爲 0 可是又不大於 1 的值的話,設置其爲 0,以進行下一次的垃圾回收。(注意,在垃圾回收標記過程當中這個值可能爲 1。垃圾回收相關在分析解釋器時再進行分析。)返回回收的字符串個數。----------------------------------------到目前爲止的問題:> luaI_codedebugline 是什麼? 調試相關信息有哪些?> do_compile 裏的 TFunc 是什麼?那個初始化 luaI_initTFunc 是什麼?> lua_parser 是什麼? do_dump 方法裏調的那幾個方法又分別是幹什麼的?----------------------------------------