版本號(hào):Lua 5.3
Lua Type
lua 的類型定義在lobject.h這個(gè)文件里蛉威,主要的類型如下:
- none
- nil
- light user data
- boolean
- number
- integer
- float
- function type
- light C function
- closure (gc object)
- lua closure
- C closure
- string (gc object)
- user data (gcobject)
- table (gc object)
- thread ( gc object)
Lua Value
lua使用一個(gè)union來(lái)統(tǒng)一表示上述類型:
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 */
};
同時(shí),添加一個(gè)額外的byte來(lái)標(biāo)記具體的類型:
#define TValuefields Value value_; int tt_ //<值雇锡,類型標(biāo)記>
struct lua_TValue {
TValuefields; // Value value_; int tt_;
};
typedef struct lua_TValue TValue;
如果展開(kāi)上述代碼箫锤,則為:
typedef struct lua_TValue{
Value value_;
int tt_;
}TValue;
其中贬蛙,tt_
是一個(gè)8 bits 的類型標(biāo)記字段,被分成3個(gè)部分:
- 0-3位谚攒,表示大類型
- 4-5位阳准,表示子類型
- 第6位,表示是否可以垃圾回收
綜合使用上面三點(diǎn)馏臭,就可以完整標(biāo)記所有的lua類型野蝇,每種類型標(biāo)記的值如下(這些定義在lua.h和lobject.h里,此處把它們合在一起括儒,更直觀):
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TNUMFLT (LUA_TNUMBER | (0 << 4)) /* float numbers */
#define LUA_TNUMINT (LUA_TNUMBER | (1 << 4)) /* integer numbers */
#define LUA_TSTRING 4
#define LUA_TSHRSTR (LUA_TSTRING | (0 << 4)) /* short strings */
#define LUA_TLNGSTR (LUA_TSTRING | (1 << 4)) /* long strings */
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TLCL (LUA_TFUNCTION | (0 << 4)) /* Lua closure */
#define LUA_TLCF (LUA_TFUNCTION | (1 << 4)) /* light C function */
#define LUA_TCCL (LUA_TFUNCTION | (2 << 4)) /* C closure */
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
#define LUA_NUMTAGS 9
#define LUA_TPROTO LUA_NUMTAGS
#define LUA_TDEADKEY (LUA_NUMTAGS+1)
#define LUA_TOTALTAGS (LUA_TPROTO + 2)
#define BIT_ISCOLLECTABLE (1 << 6)
Value是一個(gè)聯(lián)合體浪耘,第一個(gè)字段是GCObject,包括:closure(lua closure+C closure), string, userdata, table, thread
塑崖,其他幾個(gè)則是非垃圾回收類型:light user data, boolean, light C function, number(integer+float)
.非垃圾回收字段被直接展開(kāi)在聯(lián)合體里,GCObject則是可垃圾回收類型的公共類:
#define CommonHeader GCObject* next;lua_byte tt; lua_byte marked
typedef struct GCObject{
CommonHeader; // GCObject* next;lua_byte tt; lua_byte marked;
};
GC Object
展開(kāi)上述GCObject代碼痛倚,則為:
typedef struct GCObject{
GCObject* next;
lua_byte tt;
lua_byte marked;
};
可見(jiàn)规婆,GCObject是以鏈表的形式串在一起。其中,tt字段是類型標(biāo)記字段抒蚜,既然TValue里已經(jīng)標(biāo)記了類型掘鄙,此處為什么重復(fù)標(biāo)記呢?我的理解是因?yàn)樵谑褂玫倪^(guò)程中嗡髓,GCObject未必是作為一個(gè)TValue傳入操漠,如果只有GCObject指針的時(shí)候,重復(fù)的tt即可使用上饿这。而marked則是在垃圾回收過(guò)程中用以標(biāo)記對(duì)象存活狀態(tài)的浊伙。
所有的GC類型,都有公共的CommonHeader頭部长捧,這是在C這種語(yǔ)言里的一種“繼承”用法嚣鄙。
TString
typedef struct TString{
CommonHeader; // GCObject* next;lua_byte tt; lua_byte marked;
lua_byte extra;
unsigned int hash;
size_t len;
struct TString* hnext;
}TString;
由于lua的string有兩個(gè)子類型:short string
和long string
。其中串结,extra字段用來(lái)標(biāo)記是否是long string哑子,hash字段則是用存儲(chǔ)在全局字符串池里的哈希值;len表示長(zhǎng)度肌割,lua的字符串并不以\0結(jié)尾卧蜓,所以需要存儲(chǔ)長(zhǎng)度信息。hnext是用來(lái)把全局TString串起來(lái)把敞,整個(gè)鏈表就是字符串池弥奸。而真正的字符串的內(nèi)容,直接存儲(chǔ)在結(jié)構(gòu)體后面的內(nèi)存里先巴,為了保證內(nèi)存的對(duì)齊其爵,對(duì)上述TString和基本類型合并做一個(gè)字節(jié)對(duì)齊:
typedef union { double u; void *s; lua_Integer i; long l; } L_Umaxalign;
typedef union UTString{
L_Umaxalign dummy;
TString tsv;
}UTString;
從而,真正的字符串內(nèi)容的內(nèi)存地址獲取如下:
/*
** Get the actual string (array of bytes) from a 'TString'.
** (Access to 'extra' ensures that value is really a 'TString'.)
*/
#define getaddrstr(ts) (cast(char *, (ts)) + sizeof(UTString))
#define getstr(ts) \
check_exp(sizeof((ts)->extra), cast(const char*, getaddrstr(ts)))
/* get the actual string (array of bytes) from a Lua value */
#define svalue(o) getstr(tsvalue(o))
UData
typedef struct Udata{
CommonHeader;
lua_byte ttuv_;// user value's tag
struct Table* metatable;
size_t len;
union Value user_; //user value
}Udata;
User Data和String的布局基本一樣伸蚯。首先是共同的CommonHeader摩渺,然后是一個(gè)類型標(biāo)記字段: ttuv_
,此處標(biāo)記的是該UserData里實(shí)際存儲(chǔ)的值(user_
字段)的類型剂邮;其次最明顯的區(qū)別是有一個(gè)Table類型的metatable
摇幻,所有對(duì)User Data的操作都會(huì)去這個(gè)metatable里查找是否有對(duì)應(yīng)的屬性或者方法定義,這也是lua的所有魔法所在挥萌。len
字段則定義了實(shí)際的數(shù)據(jù)長(zhǎng)度绰姻,同時(shí)還有一個(gè)附加的用戶定義值字段:user_
。
User Data和String一樣把額外的數(shù)據(jù)塊存在結(jié)構(gòu)體后面的內(nèi)存里引瀑,同樣地對(duì)起始地址做了對(duì)齊:
typedef union UUdata{
L_Umaxalign dummy;
Udata uv;
}UUdata;
從而狂芋,User Data的額外數(shù)據(jù)塊的地址如下,注意:
/*
** Get the address of memory block inside 'Udata'.
** (Access to 'ttuv_' ensures that value is really a 'Udata'.)
*/
#define getudatamem(u) \
check_exp(sizeof((u)->ttuv_), (cast(char*, (u)) + sizeof(UUdata)))
另外憨栽,對(duì)于User Data來(lái)說(shuō)帜矾,metatable是每個(gè)實(shí)例一個(gè)翼虫,user_
和ttuv_
兩個(gè)字段則是值部分。所以設(shè)置和獲取UserData的接口如下:
#define setuservalue(L,u,o) \
{ const TValue *io=(o); Udata *iu = (u); \
iu->user_ = io->value_; iu->ttuv_ = io->tt_; \
checkliveness(G(L),io); }
#define getuservalue(L,u,o) \
{ TValue *io=(o); const Udata *iu = (u); \
io->value_ = iu->user_; io->tt_ = iu->ttuv_; \
checkliveness(G(L),io); }
總之屡萤,UserData=tag+value+metatable=data+metatable珍剑;
Table
typedef struct Table {
CommonHeader;
lu_byte flags; /* 1<<p means tagmethod(p) is not present */
lu_byte lsizenode; /* log2 of size of 'node' array */
unsigned int sizearray; /* size of 'array' array */
TValue *array; /* array part */
Node *node;
Node *lastfree; /* any free position is before this position */
struct Table *metatable;
GCObject *gclist;
} Table;
首先,類似User Data死陆,Table也包括data和metatable招拙,其中metatable的構(gòu)成如下:
lu_byte flags; // 1<<p means tagmethod(p) is not present
struct Table* metatable;
如果要判斷某個(gè)預(yù)定義下標(biāo)的元方法是否存在,可以通過(guò)1<<p來(lái)判斷措译,如果有别凤,則從metatable里獲取。
其次瞳遍,Table包括array部分和hash table部分闻妓,array部分如下:
// array
unsigned int sizearray;
TValue* array;
而hash table部分如下:
// hash table
lu_byte lsizenode;
Node* node;
Node* lastfree;
Node就是一個(gè)key-value,通過(guò)key部分的鏈表串在一起:
typedef struct Node {
TValue i_val;
TKey i_key;
} Node;
typedef union TKey {
struct {
TValuefields;
int next; /* for chaining (offset for next node) */
} nk;
TValue tvk;
} TKey;
最后掠械,gclist是用以垃圾回收的由缆,按下不表。從而猾蒂,我們可以重新調(diào)整下Table的聲明順序均唉,使得更利于閱讀:
typedef struct Table{
CommonHeader;
lu_byte flags; // 1<<p means tagmethod(p) is not present
struct Table* metatable;
lu_byte lsizenode;
Node* node;
Node* lastfree;
unsigned int sizearray;
TValue* array;
GCObject* gclist;
}Table;
Closure
到了最復(fù)雜的Closure部分。根據(jù)前面的鋪墊肚菠,我們知道Lua的函數(shù)包括Lua Closure, light C function以及 C Closure三種小類型舔箭,其中l(wèi)ight C function就是純c函數(shù),在Value的定義里直接用一個(gè)lua_CFunction函數(shù)指針指向蚊逢,從而剩下兩個(gè)Closure類型层扶。
lua的源碼里把Lua Closure和 C Closure作為一個(gè)聯(lián)合體,構(gòu)成了Closure類型:
typedef union Closure{
CClosure c;
LClosure l;
}Closure;
Closure作為一個(gè)GC Object烙荷,自然需要包含CommonHeader镜会,由于是一個(gè)聯(lián)合體,所以這個(gè)CommonHeader就分別拆到了CClosure和LClosure里去了:
#define ClosureHeader \
CommonHeader; lu_byte nupvalues; GCObject *gclist
typedef struct CClosure {
ClosureHeader;
lua_CFunction f;
TValue upvalue[1]; /* list of upvalues */
} CClosure;
typedef struct LClosure {
ClosureHeader;
struct Proto *p;
UpVal *upvals[1]; /* list of upvalues */
} LClosure;
注意终抽,這里CommonHeader+nupvalues+gclist共同構(gòu)成了ClosureHeader戳表,這是因?yàn)閮煞NClosure都有公共的部分:nupvalues
說(shuō)明閉包變量的個(gè)數(shù),gclist則用以垃圾回收昼伴。
我們先看比較簡(jiǎn)單的CClosure匾旭,就是直接把lua_CFunction加上被閉包的c變量upvalue[1]數(shù)組,此處利用數(shù)組在結(jié)構(gòu)的末尾圃郊,則只需聲明為一個(gè)元素的數(shù)組即可价涝。
比較復(fù)雜的是LClosure,中間的關(guān)鍵結(jié)構(gòu)是Proto* p; 這個(gè)字段代表了一個(gè)Lua 閉包持舆。我們一步步展開(kāi):
/*
** Function Prototypes
*/
typedef struct Proto {
CommonHeader;
lu_byte numparams; /* number of fixed parameters */
lu_byte is_vararg;
lu_byte maxstacksize; /* maximum stack used by this function */
int sizeupvalues; /* size of 'upvalues' */
int sizek; /* size of 'k' */
int sizecode;
int sizelineinfo;
int sizep; /* size of 'p' */
int sizelocvars;
int linedefined;
int lastlinedefined;
TValue *k; /* constants used by the function */
Instruction *code;
struct Proto **p; /* functions defined inside the function */
int *lineinfo; /* map from opcodes to source lines (debug information) */
LocVar *locvars; /* information about local variables (debug information) */
Upvaldesc *upvalues; /* upvalue information */
struct LClosure *cache; /* last created closure with this prototype */
TString *source; /* used for debug information */
GCObject *gclist;
} Proto;
調(diào)整字節(jié)對(duì)齊后的結(jié)構(gòu)體并不利于閱讀飒泻,我們不妨重新排版下:
typedef struct Proto{
CommonHeader;
// 1
lu_byte numparams;
lu_byte is_vararg;
lu_byte maxstacksize;
// 2
int sizek;
TValue* k;
// 3
int sizelocalvars;
LocVar* locvars;
// 4
int sizeupvalues;
Upvaldesc* upvalues;
// 5
int sizep;
struct Proto** p;
struct LClosure* cache;
// 6
int sizecode;
Instruction* code;
// 7
int sizelineinfo;
int* lineinfo;
// 8
int linedefined;
int lastlinedefined;
TString* source;
// 9
GCObject* gclist;
}Proto;
- 函數(shù)原型信息
-
num params
: 函數(shù)參數(shù)個(gè)數(shù) -
is_vararg
: 是否是有變長(zhǎng)參數(shù) -
maxstacksize
: 最大的函數(shù)棧長(zhǎng)度
- 常量
-
sizek
: 常量數(shù)組長(zhǎng)度 -
k
: 常量數(shù)組
- 局部變量
-
sizelocalvars
:局部變量數(shù)組長(zhǎng)度 -
localvars
: 局部變量數(shù)組
- 閉包變量
-
sizeupvalues
: 閉包變量數(shù)組長(zhǎng)度 -
upvalus
: 閉包變量數(shù)組
- 嵌套的Proto:
-
sizep
:嵌套的Proto數(shù)組長(zhǎng)度 -
p
:嵌套的Proto數(shù)組 -
cache
: 緩存嵌套的Proto的閉包鞭光。
- Proto代表一個(gè)可執(zhí)行函數(shù),前面的信息都是數(shù)據(jù)部分(參數(shù)泞遗、常量、局部變量席覆、閉包變量)史辙,此處是指令:
-
sizecode
:指令數(shù)組的長(zhǎng)度 -
code
:三地址指令數(shù)組,后面單獨(dú)講解佩伤。
- 行信息聊倔,用以debug,每行指令都有對(duì)應(yīng)的行信息生巡。
-
sizelineinfo
:行信息數(shù)組長(zhǎng)度 -
lineinfo
:行信息數(shù)組
- 源碼
-
linedefined
和lastlinedefined
:函數(shù)的起始定義行號(hào) -
source
:源碼字符串耙蔑。
- gclist,垃圾回收專用孤荣,后面講解甸陌。
到此為止,我們把Proto的字段分拆一個(gè)閉包函數(shù)所需要的每個(gè)部分盐股,更易于理解钱豁。但還有幾個(gè)小模塊。
LocVar
/*
** 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;
LocVar的定義疯汁,包括變量名+作用域牲尺。
Upvaldesc
/*
** Description of an upvalue for function prototypes
*/
typedef struct Upvaldesc {
TString *name; /* upvalue name (for debug information) */
lu_byte instack; /* whether it is in stack */
lu_byte idx; /* index of upvalue (in stack or in outer function's list) */
} Upvaldesc;
Upvaldesc只是描述了閉包變量的信息:是否在棧上+在棧上的Index。這里只有描述信息幌蚊,那么閉包變量的值存儲(chǔ)在哪里呢谤碳?
我們回頭看下LClosure的定義:
typedef struct LClosure {
ClosureHeader;
struct Proto *p;
UpVal *upvals[1]; /* list of upvalues */
} LClosure;
注意看,這里和CClosure不同的是溢豆,CClosure直接用TValue數(shù)組存儲(chǔ)閉包變量蜒简,但LClosure則是用UpVal數(shù)組。我們看下UpVal沫换。
UpVal
/*
** Upvalues for Lua closures
*/
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) */
} u;
};
UpVal定義在lfunc.h文件里臭蚁,這里第一個(gè)字段v
就是指向了閉包變量的真正的值的指針。refcount是被閉包的引用計(jì)數(shù)讯赏,按下不談垮兑。單說(shuō)后面的聯(lián)合體:
union {
struct { /* (when open) */
UpVal *next; /* linked list */
int touched; /* mark to avoid cycles with dead threads */
} open;
TValue value; /* the value (when closed) */
} u;
注意看,聯(lián)合體內(nèi)部有一個(gè)open結(jié)構(gòu)和一個(gè)value字段漱挎。一個(gè)Proto在外層函數(shù)沒(méi)有返回之前系枪,處于open狀態(tài),閉包的變量磕谅,直接通過(guò)UpVal ->v這個(gè)指針引用私爷。此時(shí)open結(jié)構(gòu)用來(lái)把當(dāng)前作用域內(nèi)的所有閉包變量都串起來(lái)做成一個(gè)鏈表雾棺,方便查找。此時(shí)u->value并沒(méi)有用到衬浑。
但是捌浩,如果外層函數(shù)返回,則Proto需要把閉包變量的值拷貝出來(lái)工秩,保證對(duì)象安全尸饺。這個(gè)拷貝就放在u->value里。此時(shí)助币,UpVal ->v也直接指向內(nèi)部的u->value浪听。
從而,我們也可以通過(guò)判斷UpVal ->v和u->value是否相等來(lái)判斷UpVal處于open還是clsoed狀態(tài):
#define upisopen(up) ((up)->v != &(up)->u.value)
待續(xù)
對(duì)象系統(tǒng)的定義部分就到這里眉菱,下次分解下對(duì)象系統(tǒng)基本屬性讀寫(xiě)的util迹栓。
......