從源碼剖析Lua數據類型


 

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

 

// 判斷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設計與實現--數據類型篇

Lua設計與實現--字符串篇

Lua設計與實現--函數篇

Lua設計與實現--Table篇

[lua source code] object system

溫故而知新

lua字符串

lua5.3.4源碼 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM