lua類型 | lua示例 | C類型(宏 ) | C子類型(variant tags宏) 及詳細說明 | C數據結構 |
nil(空) | type(nil) -->nil | #define LUA_TNIL 0 //空類型 | // 判斷TValue* o是否為一個nil 即:o->_tt是否為0 #define ttisnil(o) checktag((o), LUA_TNIL) |
無 |
boolean(布爾) | type(true) -->boolean | #define LUA_TBOOLEAN 1 | 不被GC管理
// 判斷TValue* o是否為一個bool值 即:o->_tt是否為1 #define ttisboolean(o) checktag((o), LUA_TBOOLEAN) |
int |
number(數值) | type(3.14) -->number type(100) -->number // 若想知道number具體類型,可使用函數math.type來獲取 math.type(3.14) -->float math.type(100) -->integer 注:對於其他非number類型,math.type會返回nil |
#define LUA_TNUMBER 3
// 判斷TValue* o是否為一個number 即:(o->_tt & 0xF)是否為3 #define ttisnumber(o) checktype((o), LUA_TNUMBER) |
#define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) /* float numbers */ 3 不被GC管理
// 判斷TValue* o是否為一個浮點值 即:o->_tt是否為3 #define ttisfloat(o) checktag((o), LUA_TNUMFLT) |
lua_Number //即double |
#define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) /* integer numbers */ 19 不被GC管理
// 判斷TValue* o是否為一個整型值 即:o->_tt是否為19(0x13) #define ttisinteger(o) checktag((o), LUA_TNUMINT) |
lua_Integer //即__int64 |
|||
string(字符串) | type("Hello World") -->string type('good') -->string |
#define LUA_TSTRING 4
// 判斷TValue* o是否為一個string 即:(o->_tt & 0xF)是否為4 #define ttisstring(o) checktype((o), LUA_TSTRING) |
#define LUA_TSHRSTR (LUA_TSTRING | (0 << 4)) /* short strings */ 4 被GC管理
// 判斷TValue* o是否為短串 即:o->_tt是否為68(0x44) #define ttisshrstring(o) checktag((o), ctb(LUA_TSHRSTR)) |
TString |
#define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) /* long strings */ 20 被GC管理
// 判斷TValue* o是否為長串 即:o->_tt是否為84(0x54) #define ttislngstring(o) checktag((o), ctb(LUA_TLNGSTR)) |
TString | |||
function(函數) |
type(print) -->function |
#define LUA_TFUNCTION 6
// 判斷TValue* o是否為一個function
#
define
ttisclosure(o) ((
rttype(o) & 0x1F) ==
LUA_TFUNCTION)
|
#define LUA_TLCL (LUA_TFUNCTION | (0 << 4)) /* Lua closure */ 6 被GC管理
// 判斷TValue* o是否為LClosure 即:o->_tt是否為70(0x46) #define ttisLclosure(o) checktag((o), ctb(LUA_TLCL))
注:Lua closure即Lua function LClosure 由 Proto 和 UpVal 組成 Proto描述了lua函數的函數原型 記錄着函數原型的字節碼、函數引用的常量表、調試信息、參數、棧大小等信息 UpVal保存了對upvalue的引用。它直接用一個TValue 指針引用一個upvalue值變量 當被引用的變量還在數據棧上時,這個指針直接指向棧上的TValue,那么這個upvalue被稱為開放的 |
LClosure |
#define LUA_TLCF (LUA_TFUNCTION | (1 << 4)) /* light C function */ 22 不被GC管理
// 判斷TValue* o是否為lua_CFunction 即:o->_tt是否為22(0x16) #define ttislcf(o) checktag((o), LUA_TLCF)
LUA_TLCF即:當LUA_TCCL不包含 upvalue時,直接用lua_CFunction函數指針,不必構造Closure對象 注:typedef int (*lua_CFunction) (lua_State *L) |
lua_CFunction | |||
#define LUA_TCCL (LUA_TFUNCTION | (2 << 4)) /* C closure */ 38 被GC管理
// 判斷TValue* o是否為CClosure 即:o->_tt是否為102(0x66) #define ttisCclosure(o) checktag((o), ctb(LUA_TCCL))
注:C closure即regular C function CClosure 由 lua_CFunction 和 TValue 組成 C 函數可以用閉包的方式嵌入 lua,與LClosure 相比,CClosure天生就是關閉的 因此,直接使用TValue來保存upvalue |
CClosure |
|||
table(表) | type({}) -->table | #define LUA_TTABLE 5 | // 判斷TValue* o是否為Table 即:o->_tt是否為69(0x45) #define ttistable(o) checktag((o), ctb(LUA_TTABLE)) 可以有元表和元方法 |
Table |
userdata(用戶數據) | type(io.stdin) -->userdata 注:stdin,stdout,stderr是lua提供三種預定義文件描述 |
#define LUA_TLIGHTUSERDATA 2 |
// 判斷TValue* o是否為一個light userdata數 即:o->_tt是否為2 #define ttislightuserdata(o) checktag((o), LUA_TLIGHTUSERDATA)
即輕量級用戶數據(light userdata) 只是一個指針(在c中,調用lua_pushlightuserdata將一個指針壓入棧來給lua使用) 沒有元表,且無法得知其類型 與數值類型一樣,不被GC管理 |
void* |
#define LUA_TUSERDATA 7 |
// 判斷TValue* o是否為一個full userdata數 即:o->_tt是否為71(0x47) #define ttisfulluserdata(o) checktag((o), ctb(LUA_TUSERDATA))
即完全用戶數據(full userdata) 通常用來表示C中的結構體,可以有元表和元方法 在c中調用lua_newuserdata創建指定大小的內存區域,被GC管理 |
void* | ||
thread(線程) | type(coroutine.create(function() end)) -->thread |
#define LUA_TTHREAD 8 | // 判斷TValue* o是否為一個thread 即:o->_tt是否為72(0x48) #define ttisthread(o) checktag((o), ctb(LUA_TTHREAD))
lua不支持真正的多線程,實際是一個協程 在c中調用lua_newstate來創建lua_State 在c中調用lua_newthread創建一個線程 被GC管理 |
lua_State |
#define LUA_TNONE (-1) //無類型 |
無 |
基礎結構
Value與TValue
lua為了方便對所有的類型進行統一管理,把它們都抽象成了一個叫做Value的union結構中 注:sizeof(Value)=8
/* ** Union of all Lua values */ typedef union Value { GCObject *gc; /* collectable objects */ void *p; /* light userdata */ int b; /* booleans */ lua_CFunction f; /* light C functions */ lua_Integer i; /* integer numbers */ lua_Number n; /* float numbers */ } Value;
從定義可以看出,主要把這些類型划分為了需要GC的類型和不需要GC的類型
由於Value是union的結構,所以每個Value實例里同時只會有一個字段是有效的
而為了知道具體哪個字段是有效的,也就是具體該Value是什么類型,從而有了TValue這個struct結構,主要在Value基礎上wrap了一個_tt字段來標識Value的具體類型 注:sizeof(TValue)=16
#define TValuefields Value value_; int tt_ typedef struct lua_TValue { TValuefields; } TValue;
GCUnion、GCObject、CommonHeader
lua把所有值按是否需要被GC,划分為了一般類型和被GC管理的類型。所有需要被GC的類型,被定義在了GCUnion里
/* ** Union of all collectable objects (only for conversions) */ union GCUnion { GCObject gc; /* common header */ struct TString ts; /* 字符串 */ struct Udata u; /* 用戶數據 */ union Closure cl; /* 函數 */ struct Table h; /* 表 */ struct Proto p; /* 函數原型:存放函數字節碼等信息 */ struct lua_State th; /* 線程 */ };
可以發現String、UserData、Closure、Table、Proto、luaState等類型都是需要被GC的,GCUnion結構和Value類似,也是同時只有一個字段是有效的
所以我們自然而然會想到,是不是類似TValue一樣,在外面給包一層type呢,但是lua實現這邊並沒有這樣做
而是讓TString、UData這些"子類"都在各自開頭定義了一個叫做CommonHeader的宏,這個宏里包含了type和一些其他字
而每個GC類型都需要在在其struct頭部定義該宏,從而可以造成一種所有GC類型都繼承自一個帶有CommonHeader宏的基類的假象
/* ** Common type for all collectable objects */ typedef struct GCObject GCObject; /* ** Common Header for all collectable objects (in macro form, to be ** included in other objects) */ #define CommonHeader GCObject *next; lu_byte tt; lu_byte marked // 注:tt,即該GC對象的具體類型 // 注:next,指向GCObject的指針,用於GC算法內部實現鏈表 // 注:marked,用於GC算法內部實現 /* ** Common type has only the common header */ struct GCObject { CommonHeader; };
這樣組織的好處在於lua可以把所有的GC類型的對象都視作是一個GCObject。
再比如,lua里創建單個gcobject的函數如下
/* ** create a new collectable object (with given type and size) and link ** it to 'allgc' list. */ GCObject *luaC_newobj (lua_State *L, int tt, size_t sz) { global_State *g = G(L); GCObject *o = cast(GCObject *, luaM_newobject(L, novariant(tt), sz)); o->marked = luaC_white(g); o->tt = tt; o->next = g->allgc; g->allgc = o; return o; }
所有的gc類型就都會調用luaC_newobj函數來創建一個GCObject實例,區別只是在於傳入的type和內存size不一樣而已
該函數會根據實際傳入的內存大小來開辟空間,然后填充CommonHeader部分的數據
最后,它還會把該obj掛接到global_state結構里定義的GC列表GCObject* allgc(保存所有gc類型對象的指針)的頭部,以供GC模塊使用
每個類型只用把創建出來的實例剩余內存部分的數據設置好即可,比如下面的String類型
#define sizelstring(l) (sizeof(union UTString) + ((l) + 1) * sizeof(char)) /* ** Get the actual string (array of bytes) from a 'TString'. ** (Access to 'extra' ensures that value is really a 'TString'.) */ #define getstr(ts) \ check_exp(sizeof((ts)->extra), cast(char *, (ts)) + sizeof(UTString)) #define gco2ts(o) \ check_exp(novariant((o)->tt) == LUA_TSTRING, &((cast_u(o))->ts)) /* ** creates a new string object */ static TString *createstrobj (lua_State *L, size_t l, int tag, unsigned int h) { TString *ts; GCObject *o; size_t totalsize; /* total size of TString object */ // 計算一個string實例實際內存占用大小:其實是UTString結構占用,再加上(charlength+1)個char大小 totalsize = sizelstring(l); // 創建GCObject o = luaC_newobj(L, tag, totalsize); ts = gco2ts(o); // 填充string實例特有字段 ts->hash = h; ts->extra = 0; // 取TString關聯的char數組 getstr(ts)[l] = '\0'; /* ending 0 */ return ts; }
string在創建完成以后,調用了內部的gco2ts函數,把本來指向GCObject指針強轉成了指向TString的指針,然后對TString的額外元數據進行了賦值
字符串
考慮到性能和內存等方面,lua把String分成短字符串和長字符串兩類來分開處理(注:長度大於40的為長串,反之則為短串;#define LUAI_MAXSHORTLEN 40)
如:短串會先在全局stringtable的hashmap結構表中查詢,若查詢不到才會創建;而長串不查詢,直接創建;兩個相同的長串將會是兩個副本,占用兩份內存。
主要原因是:
① 短串復用度會比長串要高。比如obj["id"] = 12, obj["type"] = 0,類似"id"、"type"這種短串可能會在程序很多處地方使用到,如果開辟多份就有點浪費了;而長串則很少會有重復的
② 長串計算哈希耗時長
TString結構體
/* ** Header for string value; string bytes follow the end of this structure ** (aligned according to 'UTString'; see next). */ typedef struct TString { CommonHeader; lu_byte extra; /* reserved words for short strings; "has hash" for longs */ lu_byte shrlen; /* length for short strings */ unsigned int hash; union { size_t lnglen; /* length for long strings */ struct TString *hnext; /* linked list for hash table */ } u; } TString;
字段含義:
CommonHeader -- GC對象通用的CommonHeader
extra -- 短串:用於實現保留字符串 長串:是否進行過hash的標識,為0時,表示該長串的hash還沒計算過,否則表示已經計算過了
shrlen -- 短串:長度 長串:未使用
hash -- 字符串的hash值。短串:該hash值是在創建時就計算出來的 長串:只有真正需要它的hash值時,才會手動調用luaS_hashlongstr函數生成該值,lua內部現在只有在把長串作為table的key時,才會去計算它。
union{lnglen, hnext} -- 短串:由於創建時會被加入到全局stringtable的鏈表中,所以在該結構中保存了指向下一個TString的指針; 長串:表示該長串的長度。
注:長串和短串沒有共用一個字段來表示它們的長度,主要是長串的長度可以很長,而短串最長就為40,一個byte就夠用了,這邊也體現了lua實現是很摳細節的,反倒是把這兩個不相關的字段打包到一個union里來節約內存了。
UTString Union
/* ** Ensures that address after this type is always fully aligned. */ typedef union UTString { L_Umaxalign dummy; /* ensures maximum alignment for strings */ TString tsv; } UTString;
為了實現TString結構的內存對齊,lua又在其上wrap了一層UTString結構
sizeof(L_Umaxalign)為8,保證UTString對象本身會按照8字節進行對齊
使用luaS_newlstr創建字符串
/* ** new string (with explicit length) 創建字符串 */ TString *luaS_newlstr (lua_State *L, const char *str, size_t l) { if (l <= LUAI_MAXSHORTLEN) /* short string? */ return internshrstr(L, str, l); // 創建短串 else { TString *ts; if (l >= (MAX_SIZE - sizeof(TString))/sizeof(char)) // MAX_SIZE=0x7FFFFFFFFFFFFFFF luaM_toobig(L); // 長度太大了,直接報錯 ts = luaS_createlngstrobj(L, l); // 創建長串 memcpy(getstr(ts), str, l * sizeof(char)); // 將str的內容拷貝到ts的內存中 return ts; } } /*************************************創建短串***************************************/ /* ** checks whether short string exists and reuses it or creates a new one */ static TString *internshrstr (lua_State *L, const char *str, size_t l) { TString *ts; global_State *g = G(L); unsigned int h = luaS_hash(str, l, g->seed); // 計算Hash 為了對長度較長的字符串不逐位hash,luaS_hash函數內部也是根據長度的2次冪計算出了一個步長step,來加速hash的過程 // 查找該Hash是否在全局stringtable對象g->strt中 TString **list = &g->strt.hash[lmod(h, g->strt.size)]; lua_assert(str != NULL); /* otherwise 'memcmp'/'memcpy' are undefined */ for (ts = *list; ts != NULL; ts = ts->u.hnext) { if (l == ts->shrlen && (memcmp(str, getstr(ts), l * sizeof(char)) == 0)) { /* found! */ if (isdead(g, ts)) /* dead (but not collected yet)? */ changewhite(ts); /* resurrect it */ // 如果在stringtable中,直接返回 return ts; } } // stringtable的元素數量已經大於桶數,那么以兩倍的尺寸對stringtable進行resize if (g->strt.nuse >= g->strt.size && g->strt.size <= MAX_INT/2) { // luaS_resize是實際改變stringtable桶數量的函數,只會在2個地方被調用 // 一個是這里:桶數量小於了元素數量,說明散列比較擁擠了,會對桶進行兩倍的擴容 // 即:newsize>oldsize。這個時候會先進行擴容,然后進行rehash。擴容跟到里面去調用的就是realloc函數。而rehash的代碼也很簡潔,就是簡單的遍歷每個桶,把每個桶里的元素再哈希到正確的桶里去 // 另一個是在gc時,如果發現桶數量大於4倍的元素數量,說明散列太稀疏了,會對桶數量進行減半操作 // 即:newsize < oldsize,順序是倒過來的,需要先根據newsize進行rehash,然后在保證所有元素已經收縮到newsize個數的桶里以后,才能進行shrink操作,這里也是調用的realloc函數來實現 luaS_resize(L, g->strt.size * 2); list = &g->strt.hash[lmod(h, g->strt.size)]; /* recompute with new size */ } // 調用createstrobj函數創建TString。其中包括了內存的分配,CommonHeader的填充,TString特化字段的填充等 ts = createstrobj(L, l, LUA_TSHRSTR, h); memcpy(getstr(ts), str, l * sizeof(char)); ts->shrlen = cast_byte(l); ts->u.hnext = *list; // 更新stringtable的鏈表,以及對stringtable的元素數量加1 *list = ts; g->strt.nuse++; return ts; } // 短串實現的hashmap結構體 typedef struct stringtable { TString **hash; // 基於TString的hashmap,也叫做散列桶。基本結構是一個數組,每個數組里存的是相同hash值的TString的鏈表 int nuse; /* number of elements */ // 當前實際的元素數 int size; // 當前的桶大小 } stringtable; /*************************************創建長串***************************************/ TString *luaS_createlngstrobj (lua_State *L, size_t l) { TString *ts = createstrobj(L, l, LUA_TLNGSTR, G(L)->seed); ts->u.lnglen = l; // 設置長串的長度變量 return ts; }
內存結構如下:
注1:將value_.gc指針強制轉換為TString*類型,即可讀取TString中數據
注2:(char*)(value_.gc) + sizeof(TString)即為字符串的內容
函數
lua函數及C函數,都是一個函數閉包。函數閉包存儲了函數本身以及外圍作用域的局部變量(upvalue)注:env環境也是upvalue的一種
#define ClosureHeader \ CommonHeader; lu_byte nupvalues; GCObject *gclist //包含了CommonHeader的宏,表明Closure是gc類型 //nupvalues為upvalue的個數 //GCObject* gclist:與gc有關,將table加入到gray表中時gclist指向gray表中的下一個元素或者為空 /* ** Upvalues for Lua closures // Lua函數閉包的Upvalues */ struct UpVal { TValue *v; /* points to stack or to its own value */ lu_mem refcount; /* reference counter */ // 引用計數 union { struct { /* (when open) */ UpVal *next; /* linked list */ int touched; /* mark to avoid cycles with dead threads */ } open; TValue value; /* the value (when closed) */ // 關閉狀態時的value值 } u; }; // C函數閉包 typedef struct CClosure { ClosureHeader; lua_CFunction f; // 函數指針 TValue upvalue[1]; /* list of upvalues */ //C函數的閉包天生是關閉的,直接使用TValue來保存upvalue } CClosure; // Lua函數閉包 typedef struct LClosure { ClosureHeader; struct Proto *p; // 函數原型(Prototype)的結構 UpVal *upvals[1]; /* list of upvalues */ //函數的upvalue指針列表,記錄了該函數引用的所有upvals } LClosure; typedef union Closure { CClosure c; LClosure l; } Closure;
Proto結構體
每個函數會被編譯成一個稱之為原型(Proto)的結構
原型主要包含6部分內容:函數基本信息(basic info:含參數數量、局部變量數量等信息)、字節碼(bytecodes)、常量(constants)表、upvalue(閉包捕獲的非局部變量)表、調試信息(debug info)、子函數原型列表(sub functions)
// Lua虛擬機的指令集為定長(Fixed-width)指令集,每條指令占4個字節(32bits),其中操作碼(OpCode)占6bits,操作數(Operand)使用剩余的26bits /* ** type for virtual-machine instructions; ** must be an unsigned with (at least) 4 bytes (see details in lopcodes.h) */ #if LUAI_BITSINT >= 32 typedef unsigned int Instruction; #else typedef unsigned long Instruction; #endif // 描述函數原型上的一個upvalue /* ** Description of an upvalue for function prototypes */ typedef struct Upvaldesc { TString *name; /* upvalue name (for debug information) */ // upvalue的名稱(debug版字節碼才有該信息) lu_byte instack; /* whether it is in stack (register) */ // 是否在寄存器中 lu_byte idx; /* index of upvalue (in stack or in outer function's list) */ // upvalue在棧或外部函數列表中index } Upvaldesc; // 描述函數原型上的一個局部變量(debug版字節碼才有該信息) /* ** Description of a local variable for function prototypes ** (used for debug information) */ typedef struct LocVar { TString *varname; // 變量名 int startpc; /* first point where variable is active */ // 起始指令索引 int endpc; /* first point where variable is dead */ // 終止指令索引 } LocVar; // 函數原型 /* ** Function Prototypes */ typedef struct Proto { CommonHeader; // GC類型 lu_byte numparams; /* number of fixed parameters */ // 固定參數個數 lu_byte is_vararg; // 是否為不定參數 lu_byte maxstacksize; /* number of registers needed by this function */ // 函數所需的寄存器數目 int sizeupvalues; /* size of 'upvalues' */ // Upvaldesc *upvalues個數 int sizek; /* size of 'k' */ // 常量TValue *k個數 int sizecode; // 指令Instruction *code個數 int sizelineinfo; // 指令int *lineinfo行號個數 (debug版字節碼才有該信息) int sizep; /* size of 'p' */ // 子函數原型個數 int sizelocvars; // 局部變量個數 int linedefined; /* debug information */ // 函數起始代碼行(debug版字節碼才有該信息) int lastlinedefined; /* debug information */ // 函數結束代碼行(debug版字節碼才有該信息) TValue *k; /* constants used by the function */ // 常量表 Instruction *code; /* opcodes */ // 指令表 struct Proto **p; /* functions defined inside the function */ // 子函數原型表 int *lineinfo; /* map from opcodes to source lines (debug information) */ // 指令行號表(debug版字節碼才有該信息) LocVar *locvars; /* information about local variables (debug information) */ // 局部變量表(debug版字節碼才有該信息) Upvaldesc *upvalues; /* upvalue information */ // upvalue表 struct LClosure *cache; /* last-created closure with this prototype */ // 最近一次使用該原型創建的closure TString *source; /* used for debug information */ // 源代碼文件名(debug版字節碼才有該信息) GCObject *gclist; // 與gc有關,將table加入到gray表中時gclist指向gray表中的下一個元素或者為空 } Proto;
這里的localvars和upvalues是函數原型中包含局部變量和upvalue的原始信息。在運行時,局部變量是存儲在Lua棧上,upvalue索引是存儲在LClosure的upvals字段中的
CClosure結構體
CClosure其實是lua在C側對閉包的一個模擬。可以通過lua_pushcclosure函數來往棧上加入一個C閉包
// 該函數會先創建一個CClosure結構,然后把提前push到棧頂的n個元素作為upvalue,將其引用存儲在CClosure的upvalue數組中 LUA_API void lua_pushcclosure (lua_State *L, lua_CFunction fn, int n) { lua_lock(L); if (n == 0) { setfvalue(L->top, fn); } else { CClosure *cl; api_checknelems(L, n); api_check(L, n <= MAXUPVAL, "upvalue index too large"); cl = luaF_newCclosure(L, n); cl->f = fn; L->top -= n; while (n--) { setobj2n(L, &cl->upvalue[n], L->top + n); /* does not need barrier because closure is white */ } setclCvalue(L, L->top, cl); } api_incr_top(L); luaC_checkGC(L); lua_unlock(L); }
CClosure和LClosure最大區別,在於LClosure是需要去解析lua代碼來得到upvalue以及字節碼等信息,在執行時需要去根據opcode來執行;
而CClosure是一個直接的C函數,可直接執行,並且upvalue也是在創建前調用者手動push到棧上去的。
表(table)
table應該算是lua最靈魂的一個結構了,它有以下特點:
容器功能:與其他語言相似,lua也內置了容器功能,也就是table。而與其他語言不同的是,lua內置容器只有table。
table的內部結構又分為了數組和哈希表(字典)兩個部分,根據不同情況來決定使用哪個部分。
面向對象功能:與其他語言不同的時,lua並沒有把面向對象的功能以語法的形式包裝給開發者。
而是保留了這樣一種能力,待開發者去實現自己的面向對象,而這一保留的能力,也是封裝在table里的。
table里可以組合一個metatable,這個metatable本身也是一個table,它的字段用來描述原table的行為。
#define TValuefields Value value_; int tt_ typedef union TKey { struct { TValuefields; // 主要是為了方便對value_、tt_變量的訪問,不用寫成tvk.value_、tvk.tt_ int next; /* for chaining (offset for next node) */ // 相當於當前Node的下一個Node的索引Offset(在當前Node后面,next值為正;在當前Node前面,next值為負) } nk; TValue tvk; } TKey; typedef struct Node { TValue i_val; // value TKey i_key; // key } Node; /* * WARNING: if you change the order of this enumeration, * grep "ORDER TM" and "ORDER OP" */ typedef enum { TM_INDEX, // flags = 00000001 TM_NEWINDEX, // flags = 00000010 TM_GC, // flags = 00000100 TM_MODE, // flags = 00001000 TM_LEN, // flags = 00010000 TM_EQ, /* last tag method with fast access */ // flags = 00100000 TM_ADD, TM_SUB, TM_MUL, TM_MOD, TM_POW, TM_DIV, TM_IDIV, TM_BAND, TM_BOR, TM_BXOR, TM_SHL, TM_SHR, TM_UNM, TM_BNOT, TM_LT, TM_LE, TM_CONCAT, TM_CALL, TM_N /* number of elements in the enum */ } TMS; #define gfasttm(g,et,e) ((et) == NULL ? NULL : \ ((et)->flags & (1u<<(e))) ? NULL : luaT_gettm(et, e, (g)->tmname[e])) // 先使用flags來判斷,若對應的位1,就立即返回NULL #define fasttm(l,et,e) gfasttm(G(l), et, e) typedef struct Table { CommonHeader; // GC類型 lu_byte flags; /* 1<<p means tagmethod(p) is not present */ // 用來快速判斷小於等於TM_EQ的tag methods是否存在:1表示不存在,0表示不確定 lu_byte lsizenode; /* log2 of size of 'node' array */ // 等於哈希表大小取log2(哈希表大小為2的次冪) unsigned int sizearray; /* size of 'array' array */ // 數組大小(數組大小只會為2的次冪) TValue *array; /* array part */ // 數組頭指針(一片連續內存) Node *node; // 哈希表頭指針(一片連續內存) Node *lastfree; /* any free position is before this position */ // 哈希表可用尾指針,可用的節點只會小於該lastfree節點 struct Table *metatable; // 元表 GCObject *gclist; // 與gc有關,將table加入到gray表中時gclist指向gray表中的下一個元素或者為空 } Table;
array和node指向的是兩個連續空間的一維數組,array是普通的數組,成員為Tvalue,node是一個hash表存放key value鍵值對。
node的key是Tkey類型,Tkey是一個聯合,當沒有hash沖突時Tkey是一個Tvalue,當有hash沖突時Tkey是一個struct多一個next值,指向下一個有沖突的節點,假設mp指向當前元素,則下一個元素為mp + next。
創建表
Table *luaH_new (lua_State *L) { GCObject *o = luaC_newobj(L, LUA_TTABLE, sizeof(Table)); Table *t = gco2t(o); t->metatable = NULL; t->flags = cast_byte(~0); // 255 二進制為:11111111 t->array = NULL; t->sizearray = 0; setnodevector(L, t, 0); return t; }
表長度
luaH_getn函數來獲取表長度
/* ** Try to find a boundary in table 't'. A 'boundary' is an integer index ** such that t[i] is non-nil and t[i+1] is nil (and 0 if t[1] is nil). */ int luaH_getn (Table *t) { unsigned int j = t->sizearray; if (j > 0 && ttisnil(&t->array[j - 1])) { // 如果sizearray>0,且數組最后一個元素為nil /* there is a boundary in the array part: (binary) search for it */ unsigned int i = 0; //二分查找 while (j - i > 1) { unsigned int m = (i+j)/2; if (ttisnil(&t->array[m - 1])) j = m; // 二分時踩到的元素為nil,則將尾部索引j向前移動到m位置 else i = m; // 否則將頭部索引i向后移動到m位置 } return i; } /* else must find a boundary in hash part */ else if (isdummy(t)) /* hash part is empty? */ // node hash表是否為空 return j; /* that is easy... */ // 為空表明為數組,直接返回j else return unbound_search(t, j); // 為非純數組時(即:里面含有hash node),就使用unbound_search來計算數組Length }
從源碼上看,table中如果有nil,會導致獲取表的長度是不准確的,下面是lua5.3.4下的一些測試
local tb1 = {0, 1, 2, nil, 4, 5, nil} -- 長度#tb1為6 local tb2 = {0, 1, nil, 3, 4, nil} -- 長度#tb2為2 local tb3 = {nil, 1, nil, 3, 4, nil} -- 長度#tb3為0 local tb4 = {0, 1, 2, nil, 4, 5} -- 長度#tb4為6 local tb5 = {0, 1, nil, 3, 4} -- 長度#tb5為5 local tb6 = {nil, 1, nil, 3, 4} -- 長度#tb6為5 local tb7 = {key1="hello"} -- 長度#tb7為0 local tb8 = {key1="hello", nil} -- 長度#tb8為0 local tb9 = {key1="hello", key2="world"} -- 長度#tb9為0 local tb10 = {key1="hello", 1, nil} -- 長度#tb10為1 local tb11 = {key1="hello", 1, 2} -- 長度#tb11為2 local tb12 = {nil, key1="hello", 1, 2} -- 長度#tb12為3 local tb13 = {key1="hello", 1, nil, 2} -- 長度#tb13為3 local tb14 = {key1="hello", 1, 2, 3, nil} -- 長度#tb14為3 local tb15 = {key1="hello", 1, nil, 2, nil} -- 長度#tb15為1
因此,在table中不要有nil,如果一個元素要刪除,直接 remove掉,不要用nil去代替
查詢
luaH_get函數傳入key來查詢value,若沒有查詢到,則返回nil。
如果key是int類型並且小於sizearray,那么直接返回數組對應slot(luaH_getint函數中),否則走hash表查詢該key對應的slot。
/* ** main search function */ const TValue *luaH_get (Table *t, const TValue *key) { // 判斷key的類型 switch (ttype(key)) { case LUA_TSHRSTR: return luaH_getshortstr(t, tsvalue(key));// 短串 case LUA_TNUMINT: return luaH_getint(t, ivalue(key)); // lua_Integer整型 case LUA_TNIL: return luaO_nilobject; case LUA_TNUMFLT: { // lua_Number浮點型 lua_Integer k; if (luaV_tointeger(key, &k, 0)) /* index is int? */ // 將浮點型key轉成整型的k return luaH_getint(t, k); /* use specialized version */ // 通過k來取值 /* else... */ } /* FALLTHROUGH */ default: return getgeneric(t, key); // 通用取值函數(效率較低) } } /* ** search function for short strings */ const TValue *luaH_getshortstr (Table *t, TString *key) { Node *n = hashstr(t, key); // 使用key->hash & (2^t->lsizenode - 1)來獲取node hash表中索引 lua_assert(key->tt == LUA_TSHRSTR); for (;;) { /* check whether 'key' is somewhere in the chain */ const TValue *k = gkey(n); if (ttisshrstring(k) && eqshrstr(tsvalue(k), key))// Node n的鍵為字符串,且與key內容相同 return gval(n); /* that's it */ // 返回Node n的值 else { int nx = gnext(n); // 獲取Node n的next if (nx == 0) return luaO_nilobject; /* not found */ n += nx; // 獲取下一個Node在node hash表中索引值n } } } /* ** search function for integers */ const TValue *luaH_getint (Table *t, lua_Integer key) { /* (1 <= key && key <= t->sizearray) */ if (l_castS2U(key) - 1 < t->sizearray) // key值小於t->sizearray,直接從數組中獲取值 return &t->array[key - 1]; else { Node *n = hashint(t, key); // 使用key & (2^t->lsizenode - 1)來獲取node hash表中索引 for (;;) { /* check whether 'key' is somewhere in the chain */ if (ttisinteger(gkey(n)) && ivalue(gkey(n)) == key) // Node n的鍵為整型,且與key數值相等 return gval(n); /* that's it */ // 返回Node n的值 else { int nx = gnext(n); // 獲取Node n的next if (nx == 0) break; n += nx; // 獲取下一個Node在node hash表中索引值n } } return luaO_nilobject; } } /* ** "Generic" get version. (Not that generic: not valid for integers, ** which may be in array part, nor for floats with integral values.) */ static const TValue *getgeneric (Table *t, const TValue *key) { Node *n = mainposition(t, key); // 使用mainposition函數來獲取key在node hash表中索引 for (;;) { /* check whether 'key' is somewhere in the chain */ if (luaV_rawequalobj(gkey(n), key)) // 判斷Node n的鍵是否與key相等 return gval(n); /* that's it */ // 返回Node n的值 else { int nx = gnext(n); // 獲取Node n的next if (nx == 0) return luaO_nilobject; /* not found */ n += nx; // 獲取下一個Node在node hash表中索引值n } } } /* ** returns the 'main' position of an element in a table (that is, the index ** of its hash value) */ static Node *mainposition (const Table *t, const TValue *key) { switch (ttype(key)) { case LUA_TNUMINT: return hashint(t, ivalue(key)); // 使用整型key & (2^t->lsizenode - 1)來獲取node hash表中索引 case LUA_TNUMFLT: return hashmod(t, l_hashfloat(fltvalue(key))); // ( l_hashfloat(浮點型key) ) % ( (2^t->lsizenode - 1) | 0x00000001 )來獲取node hash表中索引 case LUA_TSHRSTR: return hashstr(t, tsvalue(key)); // 使用key->hash & (2^t->lsizenode - 1)來獲取node hash表中索引 case LUA_TLNGSTR: return hashpow2(t, luaS_hashlongstr(tsvalue(key))); // 如果長串沒有計算過hash,則調用luaS_hashlongstr來計算,然后再使用hash & (2^t->lsizenode - 1)來獲取node hash表中索引 case LUA_TBOOLEAN: return hashboolean(t, bvalue(key)); // 使用整型key & (2^t->lsizenode - 1)來獲取node hash表中索引 case LUA_TLIGHTUSERDATA: return hashpointer(t, pvalue(key)); // 使用指針(key & 0xffffffff) % ( (2^t->lsizenode - 1) | 0x00000001 )來獲取node hash表中索引 case LUA_TLCF: return hashpointer(t, fvalue(key)); // 使用指針(key & 0xffffffff) % ( (2^t->lsizenode - 1) | 0x00000001 )來獲取node hash表中索引 default: lua_assert(!ttisdeadkey(key)); return hashpointer(t, gcvalue(key)); } } #define luaV_rawequalobj(t1,t2) luaV_equalobj(NULL,t1,t2) // 判斷t1和t2數是否相等 /* ** Main operation for equality of Lua values; return 't1 == t2'. ** L == NULL means raw equality (no metamethods) */ int luaV_equalobj (lua_State *L, const TValue *t1, const TValue *t2) { const TValue *tm; if (ttype(t1) != ttype(t2)) { /* not the same variant? */ // 類型不一樣 if (ttnov(t1) != ttnov(t2) || ttnov(t1) != LUA_TNUMBER) // t1 t2不是整型、浮點 return 0; /* only numbers can be equal with different variants */ else { /* two numbers with different variants */ lua_Integer i1, i2; /* compare them as integers */ return (tointeger(t1, &i1) && tointeger(t2, &i2) && i1 == i2); // 如果是浮點轉成整型,再進行比較 } } /* values have same type and same variant */ switch (ttype(t1)) { case LUA_TNIL: return 1; // 同為空類型,則相等 case LUA_TNUMINT: return (ivalue(t1) == ivalue(t2)); // 同為整型,比較數值 case LUA_TNUMFLT: return luai_numeq(fltvalue(t1), fltvalue(t2)); // 同為浮點型,比較數值 case LUA_TBOOLEAN: return bvalue(t1) == bvalue(t2); /* true must be 1 !! */ // 同為bool型(true:1 false:0),比較數值 case LUA_TLIGHTUSERDATA: return pvalue(t1) == pvalue(t2); // 同為指針,比較數值 case LUA_TLCF: return fvalue(t1) == fvalue(t2); // 同為函數指針,比較數值 case LUA_TSHRSTR: return eqshrstr(tsvalue(t1), tsvalue(t2));// 同為短串,比較指針 case LUA_TLNGSTR: return luaS_eqlngstr(tsvalue(t1), tsvalue(t2));// 同為長串,逐內容比較 case LUA_TUSERDATA: { if (uvalue(t1) == uvalue(t2)) return 1; else if (L == NULL) return 0; tm = fasttm(L, uvalue(t1)->metatable, TM_EQ); if (tm == NULL) tm = fasttm(L, uvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } case LUA_TTABLE: { if (hvalue(t1) == hvalue(t2)) return 1; else if (L == NULL) return 0; tm = fasttm(L, hvalue(t1)->metatable, TM_EQ); if (tm == NULL) tm = fasttm(L, hvalue(t2)->metatable, TM_EQ); break; /* will try TM */ } default: return gcvalue(t1) == gcvalue(t2); } if (tm == NULL) /* no TM? */ return 0; /* objects are different */ luaT_callTM(L, tm, t1, t2, L->top, 1); /* call TM */ // 通過TM_EQ的tag methods來判斷相等性 return !l_isfalse(L->top);// 獲取luaT_callTM返回值 }
新增
新增元素核心是通過luaH_newkey函數來新增key
/* ** beware: when using this function you probably need to check a GC ** barrier and invalidate the TM cache. */ TValue *luaH_set (lua_State *L, Table *t, const TValue *key) { const TValue *p = luaH_get(t, key); // 獲取key對應value值 if (p != luaO_nilobject) // value值不為空 return cast(TValue *, p); // 返回該value值 else return luaH_newkey(L, t, key); // 否則新建一個key,並返回value值 } /* ** inserts a new key into a hash table; first, check whether key's main ** position is free. If not, check whether colliding node is in its main ** position or not: if it is not, move colliding node to an empty place and ** put new key in its main position; otherwise (colliding node is in its main ** position), new key goes to an empty position. */ TValue *luaH_newkey (lua_State *L, Table *t, const TValue *key) { Node *mp; TValue aux; if (ttisnil(key)) luaG_runerror(L, "table index is nil"); // key為nil,直接報錯 else if (ttisfloat(key)) { // key為浮點數 lua_Integer k; if (luaV_tointeger(key, &k, 0)) { /* does index fit in an integer? */ // 將浮點數key轉成整型 setivalue(&aux, k); key = &aux; /* insert it as an integer */ } else if (luai_numisnan(fltvalue(key))) // 浮點數為NaN,直接報錯 luaG_runerror(L, "table index is NaN"); } mp = mainposition(t, key); // 使用mainposition函數來獲取key在node hash表中索引 if (!ttisnil(gval(mp)) || isdummy(t)) { /* main position is taken? */ // mp節點的value有值,或者node hash表為空,需要創建新的節點 Node *othern; Node *f = getfreepos(t); /* get a free place */ // 返回空閑Node if (f == NULL) { /* cannot find a free place? */ // node hash表沒有空閑node rehash(L, t, key); /* grow table */ // 擴容表空間 /* whatever called 'newkey' takes care of TM cache */ return luaH_set(L, t, key); /* insert key into grown table */ // 將key插入到擴張后的表中 } lua_assert(!isdummy(t)); othern = mainposition(t, gkey(mp)); // 獲取占用節點在node hash表中索引 if (othern != mp) { /* is colliding node out of its main position? */ // 如果占用節點的索引與插入key的不同,說明該節點是被“擠”到該位置來的,那么把該節點挪到freepos去,然后讓newkey入住其mainposition /* yes; move colliding node into free position */ while (othern + gnext(othern) != mp) /* find previous */ othern += gnext(othern); gnext(othern) = cast_int(f - othern); /* rechain to point to 'f' */ *f = *mp; /* copy colliding node into free pos. (mp->next also goes) */ if (gnext(mp) != 0) { gnext(f) += cast_int(mp - f); /* correct 'next' */ gnext(mp) = 0; /* now 'mp' is free */ } setnilvalue(gval(mp)); } else { /* colliding node is in its own main position */ // 占用的節點和newkey的哈希值相同,那么直接插入到該mainposition的next 即:從mainposition鏈表頭部插入newkey /* new node will go into free position */ if (gnext(mp) != 0) gnext(f) = cast_int((mp + gnext(mp)) - f); /* chain new position */ else lua_assert(gnext(f) == 0); gnext(mp) = cast_int(f - mp); mp = f; } } setnodekey(L, &mp->i_key, key); // 將key賦值給mp->i_key luaC_barrierback(L, t, key); // 如果表t是black(垃圾),這將其標色為gray,防止被gc掉 lua_assert(ttisnil(gval(mp))); return gval(mp); // 返回mp的value值 } static Node *getfreepos (Table *t) { if (!isdummy(t)) { // node hash表不為空 while (t->lastfree > t->node) { // 哈希表可用尾指針大於首指針 t->lastfree--; // 可用尾指針向前移動 if (ttisnil(gkey(t->lastfree))) // 判斷是否被使用 return t->lastfree; // 沒有被使用,則返回該指針 } } return NULL; /* could not find a free place */ }
擴展大小
當在node hash表中找不到空閑節點時,就會調用rehash函數來擴展數組和擴展node hash表的大小。
/* ** nums[i] = number of keys 'k' where 2^(i - 1) < k <= 2^i */ static void rehash (lua_State *L, Table *t, const TValue *ek) { unsigned int asize; /* optimal size for array part */ // 最終數組的大小(一定為2的次冪) unsigned int na; /* number of keys in the array part */ // 最終歸入數組部分的key的個數 unsigned int nums[MAXABITS + 1]; // MAXABITS=31 它的第i個位置存儲的是key在2^(i-1)~2^i區間內的數量 int i; int totaluse; // 總共的key個數 for (i = 0; i <= MAXABITS; i++) nums[i] = 0; /* reset counts */ na = numusearray(t, nums); /* count keys in array part */ // 遍歷當前表的array部分,按其中key的分布來更新nums數組 totaluse = na; /* all those keys are integer keys */ totaluse += numusehash(t, nums, &na); /* count keys in hash part */ // 遍歷當前的hash表部分,如果其中的key為整數,na++並且更新nums數組,對於每個遍歷的元素,totaluse++ /* count extra key */ na += countint(ek, nums); // 將ek是整型的,更新nums數組,並返回1 totaluse++; // 計算optimal的array部分大小。這個函數根據整型key在2^(i-1)~2^i之間的填充率,來決定最終的array大小。 // 一旦遇到某個子區間的填充率小於1/2,那么后續的整型key都存儲到hash表中去,這一步是為了防止數組過於稀疏而浪費內存 /* compute new size for array part */ asize = computesizes(nums, &na); // 根據上一步計算出的最終數組和哈希表大小,進行resize操作。如果哈希表的尺寸有變化,會對原來哈希表中的元素進行真正的rehash /* resize the table to new computed sizes */ luaH_resize(L, t, asize, totaluse - na); }
迭代器
在使用測主要是ipairs和pairs兩個函數。這兩個函數都會在vm內部臨時創建出兩個變量state和index,用於對lua表進行迭代訪問,每次訪問的時候,會調用luaH_next函數
int luaH_next (lua_State *L, Table *t, StkId key) { // 返回key在表中的索引i unsigned int i = findindex(L, t, key); /* find original element */ // i < t->sizearray,表明key存放在數組中 for (; i < t->sizearray; i++) { /* try first array part */ if (!ttisnil(&t->array[i])) { /* a non-nil value? */ setivalue(key, i + 1); setobj2s(L, key+1, &t->array[i]); return 1; } } // 否則key在node hash表中 for (i -= t->sizearray; cast_int(i) < sizenode(t); i++) { /* hash part */ if (!ttisnil(gval(gnode(t, i)))) { /* a non-nil value? */ setobj2s(L, key, gkey(gnode(t, i))); setobj2s(L, key+1, gval(gnode(t, i))); return 1; } } return 0; /* no more elements */ }
userdata(用戶數據)
Lua和C交互所使用的的自定義數據分為full userdata和light userdata兩個子類,這兩者的根本區別在於內存生命周期的管理者不同。
full userdata的內存在Lua棧上分配。用戶使用userdata時,通過調用lua_newuserdata(lua_State* L, size_t nBytes)分配指定大小的內存塊,類似於malloc,但不需要自行調用free(),該內存由Lua的gc機制進行回收。
full userdata可認為是lua中不需要理解/解析的可存儲任意數據的原始內存區域。在lua中僅需獲取userdata,並調用其函數接口;c/c++則負責理解userdata,並實現其函數接口功能。
light userdata只是通過lua_pushlightuserdata(ua_state* L, void* p)將C對象指針交給Lua的對象持有,light userdata所使用的內存的分配和回收,需要用戶自行管理,Lua並不會幫忙回收。
尤其需要注意的是,Lua中light userdata的對象生命周期與綁定C對象的生命周期息息相關,因此C對象釋放時,Lua中的light userdata的釋放也需要用戶關心處理,否則會出現野指針問題。
Udata結構體與UUdata Union
/* ** Header for userdata; memory area follows the end of this structure ** (aligned according to 'UUdata'; see next). */ typedef struct Udata { CommonHeader; lu_byte ttuv_; /* user value's tag */ // Udata存放的類型 struct Table *metatable; // userdata的元表,和table的元表一樣的 size_t len; /* number of bytes */ // 使用userdata的時候綁定對象申請的空間大小 union Value user_; /* user value */ // Udata存放的value } Udata; // 為了實現Udata結構的內存對齊,lua又在其上wrap了一層UUdata結構 /* ** Ensures that address after this type is always fully aligned. */ typedef union UUdata { // sizeof(L_Umaxalign)為8,保證UUdata對象本身會按照8字節進行對齊 L_Umaxalign dummy; /* ensures maximum alignment for 'local' udata */ Udata uv; } UUdata;
使用luaS_newudata創建full userdata
Udata *luaS_newudata (lua_State *L, size_t s) { Udata *u; GCObject *o; if (s > MAX_SIZE - sizeof(Udata)) luaM_toobig(L); o = luaC_newobj(L, LUA_TUSERDATA, sizeludata(s)); u = gco2u(o); u->len = s; u->metatable = NULL; setuservalue(L, u, luaO_nilobject); return u; }
內存結構如下:
注:將value_.gc指針強制轉換為Udata*類型,即可讀取Udata中數據
void *(lua_touserdata) (lua_State *L, int idx)函數可以返回索引為idx處的userdata指針
當為light userdata時,返回的是value_.p
當為full userdata時,返回的是full userdata內存塊的首地址:value_.gc + sizeof(Udata)
thread(線程)
從Lua的使用者的角度看,global_State是不可見的。我們無法用公開的API取到它的指針,也不需要引用它。但分析Lua的實現就不能繞開這個部分。
global_State里面有對主線程的引用,有注冊表管理所有全局數據,有全局字符串表,有內存管理函數,
有GC需要的把所有對象串聯起來的相關信息,以及一切Lua在工作時需要的工作內存。
typedef struct global_State { lua_Alloc frealloc; /* function to reallocate memory */ void *ud; /* auxiliary data to 'frealloc' */ l_mem totalbytes; /* number of bytes currently allocated - GCdebt */ l_mem GCdebt; /* bytes allocated not yet compensated by the collector */ lu_mem GCmemtrav; /* memory traversed by the GC */ lu_mem GCestimate; /* an estimate of the non-garbage memory in use */ stringtable strt; /* hash table for strings */ TValue l_registry; unsigned int seed; /* randomized seed for hashes */ lu_byte currentwhite; lu_byte gcstate; /* state of garbage collector */ lu_byte gckind; /* kind of GC running */ lu_byte gcrunning; /* true if GC is running */ GCObject *allgc; /* list of all collectable objects */ GCObject **sweepgc; /* current position of sweep in list */ GCObject *finobj; /* list of collectable objects with finalizers */ GCObject *gray; /* list of gray objects */ GCObject *grayagain; /* list of objects to be traversed atomically */ GCObject *weak; /* list of tables with weak values */ GCObject *ephemeron; /* list of ephemeron tables (weak keys) */ GCObject *allweak; /* list of all-weak tables */ GCObject *tobefnz; /* list of userdata to be GC */ GCObject *fixedgc; /* list of objects not to be collected */ struct lua_State *twups; /* list of threads with open upvalues */ unsigned int gcfinnum; /* number of finalizers to call in each GC step */ int gcpause; /* size of pause between successive GCs */ int gcstepmul; /* GC 'granularity' */ lua_CFunction panic; /* to be called in unprotected errors */ struct lua_State *mainthread; const lua_Number *version; /* pointer to version number */ TString *memerrmsg; /* memory-error message */ TString *tmname[TM_N]; /* array with tag-method names */ struct Table *mt[LUA_NUMTAGS]; /* metatables for basic types */ TString *strcache[STRCACHE_N][STRCACHE_M]; /* cache for strings in API */ } global_State; /* ** 'per thread' state */ struct lua_State { CommonHeader; unsigned short nci; /* number of items in 'ci' list */ lu_byte status; StkId top; /* first free slot in the stack */ global_State *l_G; CallInfo *ci; /* call info for current function */ const Instruction *oldpc; /* last pc traced */ StkId stack_last; /* last free slot in the stack */ StkId stack; /* stack base */ UpVal *openupval; /* list of open upvalues in this stack */ GCObject *gclist; struct lua_State *twups; /* list of threads with open upvalues */ struct lua_longjmp *errorJmp; /* current error recover point */ CallInfo base_ci; /* CallInfo for first level (C calling Lua) */ volatile lua_Hook hook; ptrdiff_t errfunc; /* current error handling function (stack index) */ int stacksize; int basehookcount; int hookcount; unsigned short nny; /* number of non-yieldable calls in stack */ unsigned short nCcalls; /* number of nested C calls */ l_signalT hookmask; lu_byte allowhook; };
創建一個lua_State
lua_newstate中初始化了主線程的數據棧、初始化注冊表、給出一個基本的字符串池、初始化元表用的字符串、初始化詞法分析用的token串、初始化內存錯誤信息。
Lua_State是暴露給用戶的數據類型。從名字上看,它想表示一個Lua程序的執行狀態,在官方文檔中,它指代Lua的一個線程。
每個線程擁有獨立的數據棧以及函數調用鏈,還有獨立的調試鈎子和錯誤處理設施。所以我們不應當簡單的把Lua_State看成一個靜態的數據集,它是一組Lua程序的執行狀態機。
所有的Lua C API都是圍繞這個狀態機,改變其狀態的:或把數據壓入堆棧,或取出,或執行棧頂的函數,或繼續上次被中斷的執行過程。
同一Lua虛擬機中的所有執行線程,共享了一塊全局數據global_State。
LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) { int i; lua_State *L; global_State *g; LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG))); if (l == NULL) return NULL; L = &l->l.l; g = &l->g; L->next = NULL; L->tt = LUA_TTHREAD; g->currentwhite = bitmask(WHITE0BIT); L->marked = luaC_white(g); preinit_thread(L, g); g->frealloc = f; g->ud = ud; g->mainthread = L; g->seed = makeseed(L); g->gcrunning = 0; /* no GC while building state */ g->GCestimate = 0; g->strt.size = g->strt.nuse = 0; g->strt.hash = NULL; setnilvalue(&g->l_registry); g->panic = NULL; g->version = NULL; g->gcstate = GCSpause; g->gckind = KGC_NORMAL; g->allgc = g->finobj = g->tobefnz = g->fixedgc = NULL; g->sweepgc = NULL; g->gray = g->grayagain = NULL; g->weak = g->ephemeron = g->allweak = NULL; g->twups = NULL; g->totalbytes = sizeof(LG); g->GCdebt = 0; g->gcfinnum = 0; g->gcpause = LUAI_GCPAUSE; g->gcstepmul = LUAI_GCMUL; for (i=0; i < LUA_NUMTAGS; i++) g->mt[i] = NULL; if (luaD_rawrunprotected(L, f_luaopen, NULL) != LUA_OK) { /* memory allocation error: free partial state */ close_state(L); L = NULL; } return L; }
把數據棧和調用棧合起來就構成了Lua中的線程。在同一個Lua虛擬機中的不同線程因為共享了global_State而很難做到真正意義上的並發。
它也絕非操作系統意義上的線程,但在行為上很相似。用戶可以resume一個線程,線程可以被yield打斷。Lua的執行過程就是圍繞線程進行的。
luaL_newstate中會用一個默認分配函數(c的malloc-realloc-free)來分配內存。如果要完全控制lua的內存分配,也可以為lua_newstate來指定內存分配函數。
如Unlua就為lua state指定了內存分配函數:
void* FLuaContext::LuaAllocator(void *ud, void *ptr, size_t osize, size_t nsize) { if (nsize == 0) { FMemory::Free(ptr); return nullptr; } void *Buffer = nullptr; if (!ptr) { Buffer = FMemory::Malloc(nsize); } else { Buffer = FMemory::Realloc(ptr, nsize); } return Buffer; } lua_State *L = lua_newstate(FLuaContext::LuaAllocator, this); // this為FLuaContext指針
創建一個thread
LUA_API lua_State *lua_newthread (lua_State *L) { global_State *g = G(L); lua_State *L1; lua_lock(L); luaC_checkGC(L); /* create new thread */ L1 = &cast(LX *, luaM_newobject(L, LUA_TTHREAD, sizeof(LX)))->l; L1->marked = luaC_white(g); L1->tt = LUA_TTHREAD; /* link it on list 'allgc' */ L1->next = g->allgc; g->allgc = obj2gco(L1); /* anchor it on L stack */ setthvalue(L, L->top, L1); api_incr_top(L); preinit_thread(L1, g); L1->hookmask = L->hookmask; L1->basehookcount = L->basehookcount; L1->hook = L->hook; resethookcount(L1); /* initialize L1 extra space */ memcpy(lua_getextraspace(L1), lua_getextraspace(g->mainthread), LUA_EXTRASPACE); luai_userstatethread(L, L1); stack_init(L1, L); /* init stack */ lua_unlock(L); return L1; }
參考
[lua source code] object system