【lua学习】7.环境
- 1 最重要的兩個數據結構
- 1.1 lua_State(Lua虛擬機/Lua協程)
- 1.2 global_State(Lua全局狀態)
- 2 環境相關的變量
- 2.1 Global表
- 2.1.1 Global表在lua_State結構中
- 2.1.2 Global表在 f_luaopen 時被初始化
- 2.2 env表
- 2.2.1 env表在Closure結構中
- 2.2.2 查找一個全局變量<=>在當前函數的env表中尋找
- 2.2.3 lua函數的env表何時被設置
- 2.2.4 C函數的env表何時被設置
- 2.2.5 getfenv(讀取函數的環境)
- 2.2.6 setfenv(強制設置函數的環境)
- 2.3 registry表(注冊表)
- 2.4 UpValue
1 最重要的兩個數據結構
1.1 lua_State(Lua虛擬機/Lua協程)
每個lua虛擬機(協程)對應一個lua_State結構體
(lstate.h) lua_State
struct lua_State {CommonHeader;//#define CommonHeader GCObject *next; lu_byte tt; lu_byte markedlu_byte status;//協程的狀態碼StkId top;//棧頂位置,“寄存器”第一個可用位置StkId base;//當前函數調用的棧基址global_State* l_G;//全局狀態機CallInfo* ci;//當前函數調用信息const Instruction* savedpc;//指令指針StkId stack_last;//“寄存器”最后一個可用位置StkId stack;//棧數組的起始位置CallInfo* end_ci;//函數調用信息數組的最后一個位置的下一個位置CallInfo* base_ci;//函數調用信息數組首地址int stacksize;//棧的大小int size_ci;//函數調用信息數組大小unsigned short nCcalls;//內嵌C調用的層數unsigned short baseCcalls;//喚醒協程時的內嵌C調用層數lu_byte hookmask;lu_byte allowhook;int basehookcount;int hookcount;lua_Hook hook;TValue l_gt;//Global表TValue env;//環境表的臨時位置GCObject* openupval;//棧上open狀態的uvaluesGCObject* gclist;struct lua_longjmp* errorJmp;//當前跳轉信息,實現try catch的關鍵結構ptrdiff_t errfunc;//當前錯誤處理函數相對于“寄存器數組首地址”的偏移地址 };1.2 global_State(Lua全局狀態)
(lstate.h) global_State
typedef struct global_State {stringtable strt;//全局字符串表lua_Alloc freealloc;//內存重分配函數void* ud;//freealloc的輔助數據lu_byte currentwhite;//當前白色,見GC章節lu_byte gcstate;//GC狀態,見GC章節int sweepstrgc;//strt中GC掃描到的位置GCObject* rootgc;//所有可回收對象的鏈表GCObject** sweepgc;//rootgc中掃描到的位置GCObject* gray;//灰色對象鏈表GCObject* grayagain;//需要被原子性遍歷地對象鏈表GCObject* week;//弱表的鏈表GCObject* tmudata;//需要被GC的userdata的鏈表的最后一個元素Mbuffer* buff;//字符串連接操作用的臨時緩沖對象lu_mem GCthreshold;//觸發GC的邊界值,見GC章節lu_mem totalbytes;//當前分配的總字節數lu_mem estimate;//實際上使用的總字節數的估計值lu_mem gcdept;//在預定的回收字節數中,還欠多少字節沒有回收int gcpause;//連續的GC中的停頓步長相關值,見GC章節int gcstepmul;//GC的粒度lua_CFunction panic;//對于未捕獲異常,會調用這個函數TValue l_registry;//注冊表struct lua_State* mainthread;//主協程UpVal uvhead;//open狀態的upvalue雙鏈表的頭部struct Table* mt[NUM_TAGS];//存放 基本類型的元表 的數組TString* tmname[TM_N];//存放 元方法名字符串對象地址 的數組 }2 環境相關的變量
2.1 Global表
Global表存放在lua_State結構體中,每個lua_State實例都有一個對應的Global表,對于一個lua_State這個表就是用來存放全局變量的。
2.1.1 Global表在lua_State結構中
struct lua_State {//..//TValue l_gt;//Global表//..// }2.1.2 Global表在 f_luaopen 時被初始化
(lstate.c) f_luaopen (f_luaopen 又被 lua_newstate 調用)
static void f_luaopen(lua_State* L, void* ud) {//...//sethvalue(L, gt(L), luaH_new(L, 0, 2));//創建了一個arraysize=0, hashsize=2的表作為全局表//...// }2.2 env表
env表存放在Closure結構體中,也就是每個函數都有自己獨立的環境
2.2.1 env表在Closure結構中
(lobject.h) Closure
#define ClosureHeader \CommonHeader; \lu_byte isC; \lu_byte nupvalues; \GCObject *gclist; \struct Table *env //函數的環境表地址typedef struct CClosure {ClosureHeader;lua_CFunction f;TValue upvalue[1]; } CClosure;typedef struct LClosure {ClosureHeader;struct Proto* p;UpVal* upvals[1]; } LClosure;typedef union Closure {CClosure c;LClosure l; } Closure;2.2.2 查找一個全局變量<=>在當前函數的env表中尋找
(lvm.c) luaV_execute 模擬CPU,后續虛擬機章節細說
//我們暫且只關心 全局變量 的 查找或設置 void luaV_execute (lua_State *L, int nexeccalls) {LClosure* cl;StkId base;TValue* k;const Instruction* pc; reentry:pc = L->savedpc;cl = &clvalue(L->ci->func)->l;base = L->base;k = cl->p->k;//循環取指令,執行指令for (;;) {const Instruction i = *pc++;//取出當前指令i,pc指向下一個指令,完全符合CPU工作原理if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)){traceexec(L, pc);//traceexec后面說if (L->status == LUA_YIELD){L->savedpc = pc - 1;return;}base = L->base;}StkId ra = RA(i);//#define RA(i) (base+GETARG_A(i))//#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))switch (GET_OPCODE(i)) {//..//case OP_GETGLOBAL: //OP_GETGLOBAL 指令格式A Bx 指令意義 R(A) := Gbl[Kst(Bx)]{TValue g;TValue* rb = KBx(i);//#define KBx(i) k+GETARG_Bx(i)sethvalue(L, &g, cl->env);//g設為去當前函數的env表Protect(luaV_gettable(L, &g, rb, ra));continue;}//..//case OP_SETGLOBAL:{TValue g;sethvalue(L, &g, cl->env);Protect(luaV_settable(L, &g, KBx(i), ra));continue;}//..//}} }(lvm.c) Protect宏
- 執行代碼x前 先將L->savedpc設為 下一步藥執行的指令地址pc (因為代碼x有異常的可能,所以記錄pc到L上以便從下一條指令繼續執行)
- 執行代碼x
- 執行代碼x后,再將 base設為 L->base(一位代碼x可能會觸發棧的重新分配內存)
2.2.3 lua函數的env表何時被設置
- f_parser函數里,env被設置為&L->l_gt(lua_State的Global表)
- luaV_execute的OP_CLOSURE指令分支,env被設置為&clvalue(L->ci->func)->l->env (當前調用信息對應的函數的env表)
(ldo.c) f_parser
static void f_parser(lua_State* L, void* ud) {struct SParser* p = cast(struct SParser*, ud);int c = luaZ_lookahead(p->z);luaC_checkGC(L);Proto* tf = ((c == LUA_SIGNATURE[0]) ? luaU_undump : luaY_parser)(L, p->z, &p->buff, p->name);//很顯然,在解析二進制或原文件時得到的lua函數,默認的env表時L的Global表Closure* cl = luaF_newLclosure(L, tf->nups, hvalue(gt(L)));cl->l.p = tf;for (i = 0; i < tf.nups; i++){cl->l.upvals[i] = luaF_newupval(L);}setclvalue(L, L->top, cl);incr_top(L); }(lvm.c) luaV_execute
//我們暫且只關心 OP_CLOSURE 指令 void luaV_execute (lua_State *L, int nexeccalls) {LClosure* cl;StkId base;TValue* k;const Instruction* pc; reentry:pc = L->savedpc;cl = &clvalue(L->ci->func)->l;base = L->base;k = cl->p->k;//循環取指令,執行指令for (;;) {const Instruction i = *pc++;//取出當前指令i,pc指向下一個指令,完全符合CPU工作原理if ((L->hookmask & (LUA_MASKLINE | LUA_MASKCOUNT)) && (--L->hookcount == 0 || L->hookmask & LUA_MASKLINE)){traceexec(L, pc);//traceexec后面說if (L->status == LUA_YIELD){L->savedpc = pc - 1;return;}base = L->base;}StkId ra = RA(i);//#define RA(i) (base+GETARG_A(i))//#define GET_OPCODE(i) (cast(OpCode, ((i)>>POS_OP) & MASK1(SIZE_OP,0)))switch (GET_OPCODE(i)) {//..//case OP_CLOSURE:{//根據索引獲取當前函數的內嵌函數原型Proto* p = cl->p->p[GETARG_Bx(i)];//獲取內嵌函數的upvalue數量int nup = p->nups;//根據upvalue數量和env表創建內嵌函數CLosure* ncl = luaF_newLclosure(L, nup, cl->env);ncl->l.p = p;for (int j = 0; j < nup; j++, pc++){if (GET_OPCODE(*pc) == OP_GETUPVAL){ncl->l.upvals[j] = cl->upvals[GETARG_B(*pc)];}else{ncl->l.upvals[j] = luaF_findupval(L, base + GETARG_B(*pc));}}setclvalue(L, ra, ncl);Protect(luaC_checkGC(L));continue;}//..//}} }(lfunc.c) luaF_newLclosure 創建一個新的lua函數(閉包)
Closure* luaF_newLclosure(lua_State* L, int nupvalues, Table* env) {//#define sizeLclosure(n) (cast(int, sizeof(LClosure)) + cast(int, sizeof(TValue *)*((n)-1))) Closure* cl = cast(Closure*, luaM_malloc(L, sizeLClosure(nupvalues)));luaC_link(L, obj2gco(cl), LUA_TFUNCTION);cl->l.isC = 0;cl->l.env = e;cl->l.nupvalues = cast_byte(nupvalues);while (nupvalues--){cl->l.upvals[nupvalues] = NULL;}return c; }2.2.4 C函數的env表何時被設置
- lua_pushcclosure函數里,env被設置為getcurrenv(L)
- f_Ccall函數里,env被設置為getcurrenv(L)
(lapi.c) lua_pushcclosure 創建一個C閉包入棧
LUA_API void lua_pushcclosure(lua_State* L, lua_CFunction fn, int n) {luaC_checkGC(L);Closure* cl = luaF_newCclosure(L, n, getcurrentv(L));cl->c.f = fn;L->top -= n;while(n--){setobj2n(L, &cl->c.upvalue[n], L->top + n);}setclvalue(L, L->top, cl);api_incr_top(L);//#define api_incr_top(L) {api_check(L, L->top < L->ci->top); L->top++;} }(lfunc.c) luaF_newCclosure 創建C閉包
Closure* luaF_newCclosure(lua_State* L, int nupvals, Table* env) {//#define sizeCclosure(n) (cast(int, sizeof(CClosure)) + cast(int, sizeof(TValue)*((n)-1)))Closure* c = cast(Closure*, luaM_malloc(L, sizeCclosure(nupvals)));luaC_link(L, obj2gco(c), LUA_TFUNCTION);c->c.isC = 1;c->c.env = env;c->c.nupvals = cast_byte(nupvals);return c; }(lapi.c) getcurrentv 獲取當前的函數env表(獲取L->ci的env表,當然L->base_ci的env表就是Global表)
- 若不是內嵌函數,則返回Global表
- 若是內嵌函數,則返回母函數的env表
(lapi.c) f_Ccall 創建并調用一個C函數
static void f_Ccall(lua_State* L, void* ud) {struct CCallS* c = cast(struct CCallS*, ud);Closure* cl = luaF_newCclosure(L, 0, getcurrenv(L));cl->c.f = c->func;setclvalue(L, L->top, cl);api_incr_top(L);luaD_call(L, L->top - 2, 0); }2.2.5 getfenv(讀取函數的環境)
(lbaselib.c) luaB_getfenv (若為C函數,則返回Global表;否則lua_getfenv)
static int luaB_getfenv(lua_State* L) {getfunc(L, 1);if (lua_iscfuntion(L, -1)){lua_pushvalue(L, LUA_GLOBALSINDEX);}else{lua_getfenv(L, -1);}return 1; }2.2.6 setfenv(強制設置函數的環境)
(lbaselib.c) luaB_setfenv
static int luaB_setfenv(lua_State* L) {//檢查L->base+2-1處是不是table類型luaL_chechtype(L, 2, LUA_TTABLE);getfunc(L, 0);//把 L->base+2-1 處的值復制到 L->top 處,并L->top++lua_pushvalue(L, 2);//若level為0,則改變當前lua線程的環境if (lua_isnumber(L, 1) && lua_tonumber(L, 1) == 0){lua_pushthread(L);lua_insert(L, -2);lua_settenv(L, -2);return 0;}//若L->top-2 是C函數,或者給 L->top-2 設置env失敗,則報錯if (lua_iscfunction(L, -2) || lua_setfenv(L, -2) == 0){//#define LUA_QL(x) "'" x "'"luaL_error(L, LUA_QL("setfenv") " cannot change environment of given object");}return 1; }(lauxlib.c) luaL_checktype 檢查類型是否滿足,否則報錯
LUALIB_API void luaL_checktype(lua_State* L, int narg, int t) {if (lua_type(L, narg) != t){tag_error(L, narg, t);} }(lauxlib.c) tag_error 根據類型標識報錯
static void tag_error(lua_State* L, int narg, int tag) {luaL_typeerror(L, narg, lua_typename(L, tag)); }(lauxlib.c) luaL_typeerror 根據類型名報錯
LUALIB_API int luaL_typeerror(lua_State* L, int narg, const char* tname) {const char* msg = lua_pushfstring(L, "%s expected, got %s", tname, luaL_typename(L, narg));return luaL_argerror(L, narg, msg);//luaL_argerror見異常章節 }(lbaselib.c) getfunc (獲取L->base 處的函數,并壓入棧頂)
static void getfunc(lua_State* L, int opt) {//若L->base處是函數,則把L->base處的值復制到L->top處,L->top++if (lua_isfunction(L, 1)){lua_pushvalue(L, 1);return;}//#define luaL_optint(L,n,d) ((int)luaL_optinteger(L, (n), (d)))//#define luaL_checkint(L,n) ((int)luaL_checkinteger(L, (n)))//opt為非0時,對L->base處的值進行判斷,若為nil或none,則取默認值,否則執行整數轉換操作;若opt為0,則僅僅執行整數轉換操作int level = opt ? luaL_optint(L, 1, 1) : luaL_checkint(L, 1);//level必須為>=0luaL_argcheck(L, level >= 0, 1, "level must be non-negative");lua_Debug ar;if (lua_getstack(L, level, &ar) == 0){luaL_argerror(L, 1, "invalid level");}lua_getinfo(L, "f", &ar);if (lua_isnil(L, -1)){luaL_error(L, "no function environmnent for tail call at level %d", level);//luaL_error見異常章節} }(lauxlib.c) luaL_optinteger
//不是數字類型且轉為整數后不是0,則報錯 LUALIB_API lua_chechinteger(lua_State* L, int narg) {lua_Integer d = lua_tointeger(L, narg);if (d == 0 && !lua_isnumber(L, narg)){tag_error(L, narg, LUA_TNUMBER);}return d; }LUALIB_API lua_Integer luaL_optinteger(lua_State* L, int narg, lua_Integer def) {//#define luaL_opt(L,f,n,d) (lua_isnoneornil(L,(n)) ? (d) : f(L,(n)))return luaL_opt(L, luaL_checkinteger, narg, def); }(ldebug.c) lua_getstack 獲取指定層級的調試信息
LUA_API int lua_getstack(lua_State* L, int level, lua_Debug* ar) {for (CallInfo* ci = L->ci; level > 0 && ci > L->base_ci; ci--){level--;//若為lua函數,則跳過尾調用,//見函數章節if (f_isLua(ci)){level -= ci->tailcalls;//原文 /* skip lost tail calls */ 見函數章節}}int status;if (level == 0 && ci > L->base_ci){//找到了合適的levelstatus = 1;ar->i_ci = cast_int(ci - L->base_ci);}else if (level < 0){//原文 /* level is of a lost tail call? */ 見函數章節status = 1;ar->i_ci = 0;}else{status = 0;}return status; }(ldebug.c) lua_getinfo 獲取debug信息
LUA_API int lua_getinfo(lua_State* L, const char* what, lua_Debug* ar) {Closure* f = NULL;if (*what == '>'){StdId func = L->top - 1;what++;f = clvalue(func);L->top--;//函數出棧}else if (ar->i_ci != 0)//沒有尾調用 //見函數章節{ci = L->base_ci + ar->i_ci;f = clvalue(ci->func);}int status = auxgetinfo(L, what, ar, f, ci);//auxgetinfo,見函數章節if (strchr(what, 'f')){if (f == NULL){setnilvalue(L->top);}else{setclvalue(L, L->top, f);}incr_top(L);}if (strchr(what, 'L')){collectvalidlines(L, f);//收集行號信息}return status; }(ldebug.c) collectvalidlines 收集行號信息(獲得一個table<行號,true> 的結構,壓入棧頂)
static void collectvalidlines(lua_State* L, Closure* f) {if (f == NULL || f->c.isC){setnilvalue(L->top);}else{Table* t = luaH_new(L, 0, 0);int* lineinfo = f->l.p->lineinfo;for (int i = 0; i < f->l.p->sizelineinfo; i++){setbvalue(luaH_setnum(L, t, lineinfo[i]), 1);}sethvalue(L, L->top, t);}incr_top(L); }2.3 registry表(注冊表)
- 注冊表在global_State中,全局唯一,這個表可以被多個lua_State訪問
- 注冊表只能被C代碼訪問,Lua代碼不能訪問
(lauxlib.h) lua_ref,lua_unref,lua_getref
#define LUA_NOREF (-2) #define LUA_REFNIL (-1)//往注冊表新增一個唯一key #define lua_ref(L,lock) ((lock) ? luaL_ref(L, LUA_REGISTRYINDEX) : \(lua_pushstring(L, "unlocked references are obsolete"), lua_error(L), 0)) //取消注冊表一個唯一key #define lua_unref(L,ref) luaL_unref(L, LUA_REGISTRYINDEX, (ref)) //獲取注冊表唯一key對應的值 #define lua_getref(L,ref) lua_rawgeti(L, LUA_REGISTRYINDEX, (ref))(lauxlib.c) luaL_ref,luaL_unref
//對于棧頂的value,從表t中獲取一個可用key,來存value /* 表中的key=FREELIST_REF對應的value是 空閑的key的索引(而所謂的空閑的key是由于unref造成的結果)舉個例子來理解這個設計: 開始t={[FREELIST_REF]=nil} 存v1: ref=t[FREELIST_REF]=nil => ref=#t + 1 = 1, t[ref]=t[1]=v1 => t={[FREELIST_REF]=nil, [1]=v1} 存v2: ref=t[FREELIST_REF]=nil => ref=#t + 1 = 2, t[ref]=t[2]=v2 => t={[FREELIST_REF]=nil, [1]=v1, [2]=v2} unref(1): ref=1, t[ref]=t[FREELIST_REF]=nil, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=nil, [2]=v2} unref(2): ref=2, t[ref=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=nil, [2]=1} 存v3: t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=1, t[ref]=v3 => t={[FREELIST_REF]=1, [1]=nil, [2]=v3} 存v4: t[FREELIST_REF]=1 => ref=1, t[FREELIST_REF]=t[ref]=nil, t[ref]=v4 => t={[FREELIST_REF]=nil, [1]=v4, [2]=v3}也就說t中的key分為2種狀態:空閑 和 非空閑。若為空閑,則其value是下一個空閑key的索引;若為非空閑,則其value是有意義的值。拓展討論: 開始t={[FREELIST_REF]=nil} 存v1: t[FREELIST_REF]=nil => ref=#t + 1 = 1, t[ref]=t[1]=v1 => t={[FREELIST_REF]=nil, [1]=v1} 存v2: t[FREELIST_REF]=nil => ref=#t + 1 = 2, t[ref]=t[2]=v2 => t={[FREELIST_REF]=nil, [1]=v1, [2]=v2} unref(1): ref=1, t[ref]=t[FREELIST_REF]=nil, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=nil, [2]=v2} 再次unref(1): ref=1, t[ref]=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=1 => t={[FREELIST_REF]=1, [1]=1, [2]=v2} unref(2): ref=2, t[ref]=t[FREELIST_REF]=1, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=1, [2]=1} 再次unref(2): ref=2, t[ref]=t[FREELIST_REF]=2, t[FREELIST_REF]=ref=2 => t={[FREELIST_REF]=2, [1]=1, [2]=2} 存v3: t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=2, t[ref]=v3 => t={[FREELIST_REF]=2, [1]=1, [2]=v3} 存v4: t[FREELIST_REF]=2 => ref=2, t[FREELIST_REF]=t[ref]=v3, t[ref]=v4 => t={[FREELIST_REF]=2, [1]=1, [2]=v4}//v3不見了,出問題了!!!*/ LUALIB_API int luaL_ref(lua_State* L, int t) {//假設棧頂的值為v//若v==nil,則返回-1if (lua_isnil(L, -1)){lua_pop(L, 1);return LUA_REFNIL;//#define LUA_REFNIL (-1)}t = abs_index(L, t);//#define abs_index(L, i) ((i) > 0 || (i) <= LUA_REGISTRYINDEX ? (i) : lua_gettop(L) + (i) + 1)lua_rawgeti(L, t, FREELIST_REF);//#define FREELIST_REF 0 /* free list of references *///ref=t[FREELIST_REF], 獲取一個空閑的keyint ref = (int)lua_tointeger(L, -1);lua_pop(L, -1);if (ref != 0)//ref!=0,說明有空閑key{lua_rawgeti(L, t, ref);lua_rawseti(L, t, FREELIST_REF);//t[FREELIST_REF] = t[ref]}else//ref==0,說明沒有空閑位置,需要新建key{ref = (int)lua_objlen(L, t);ref++;//創建一個新的引用}lua_rawseti(L, t, ref);//t[ref]=vreturn ref; }LUALIB_API void luaL_unref(lua_State* L, int t, int ref) {if (ref < 0){return;}t = abs_index(L, t);lua_rawgeti(L, t, FREELIST_REF);lua_rawseti(L, t, ref);//t[ref]=t[FREELIST_REF]lua_pushinteger(L, ref);lua_rawseti(L, t, FREELIST_REF);//t[FREELIST_REF]=ref }(lapi.c) lua_objlen 獲取對象的長度
LUA_API size_t lua_objlen(lua_State* L, int idx) {StkId o = index2adr(L, idx);switch(ttype(o)){case LUA_TSTRING:{return tsvalue(o)->len;}case LUA_TUSERDATA:{return uvalue(o)->len;}case LUA_TTABLE:{return luaH_getn(hvalue(o));}case LUA_TNUMBER:{size_t l = luaV_tostring(L, o) ? tsvalue(o)->len : 0;return l;}default:{return 0;}} }2.4 UpValue
registry表是全局變量的存儲,env表是函數內全局變量的存儲,UpValue則是函數內靜態變量的存儲
通過 index2adr(L, int idx) (其中idx<LUA_GLOBALSINDEX)獲取C閉包的upvalue地址
至于UpValue的一切,見函數章節。
總結
以上是生活随笔為你收集整理的【lua学习】7.环境的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深圳电动车备案后多久可以拿牌(深圳电动车
- 下一篇: 【lua学习】5.栈和lua_State