lua中有兩種閉包, c閉包和lua閉包 兩種閉包的公共部分: C閉包的結構 結構比較簡單, f是一個滿足 int lua_func(lua_State ) 類型的c函數 upvalue是創建C閉包時壓入的upvalue, 類型是TValue, 可以得知, upvalue可以是任意的lua類型 Lu ...
lua中有兩種閉包, c閉包和lua閉包
兩種閉包的公共部分:
#define ClosureHeader CommonHeader;\
lu_byte isC; \ 是否c閉包
lua_byte nupvalues; \ upvalue的個數
GCObject* gclist; \
struct Table env 閉包的環境
C閉包的結構
struct CClosure{
ClosureHeader;
lua_CFunction f;
TValue upvalue[1];
}
結構比較簡單, f是一個滿足 int lua_func(lua_State*) 類型的c函數
upvalue是創建C閉包時壓入的upvalue, 類型是TValue, 可以得知, upvalue可以是任意的lua類型
Lua閉包結構
struct LClosure{
ClosureHeader;
strcut Proto* p;
UpVal* upvals[1];
}
Proto的結構比較複雜, 這裡先不做分析
統一的閉包結構, 一個聯合體, 說明一個閉包要麼是C閉包, 要麼是lua閉包, 這個是用isC表識出來的.
union Closure{
CClosure c;
LClosure l;
}
閉包 == {功能抽象, upvalue, env}
向lua中註冊c函數的過程是通過lua_pushcclosure(L, f, n)函數實現的
流程:
創建一個 sizeof(CClosure) + (n - 1) * sizeof(TValue)大小的記憶體, 這段記憶體是 CClosure + TValue[n],, isC= 1 標示其是一個C閉包.
c->f = f綁定c函數. --------- 閉包.功能抽象 = f
env = 當前閉包的env. ---------- 閉包.env = env
把棧上的n個元素賦值到c->upvalue[]數組中, 順序是越先入棧的值放在upvalue數組的越開始位置, c->nupvalues指定改閉包upvalue的個數. ---------- 閉包.upvalue = upvalue
彈出棧上n個元素, 並壓入新建的Closure到棧頂.
整個流程是: 分配記憶體, 填寫屬性, 鏈入gc監控, 綁定c函數, 綁定upvalue, 綁定env一個C閉包就ok了
C閉包被調用的過程
lua 閉包調用信息結構:
struct CallInfo{
StkId base; ----閉包調用的棧基
StkId func; ----要調用的閉包在棧上的位置
StkId top; ----閉包的棧使用限制
const Instruction *savedpc; ----如果在本閉包中再次調用別的閉包, 那麼該值就保存下一條指令以便在返回時繼續執行
int nresults; ----閉包要返回的值個數
int tailcalls;----尾遞歸用, 暫時不管
}
這個結構是比較簡單的, 它的作用就是維護一個函數調用的有關信息, 其實和c函數調用的棧幀是一樣的, 重要的信息base –> ebp, func –> 要調用的函數的棧index, savedpc –> eip, top, nresults和tailcalls沒有明顯的對應.
在lua初始化的時候, 分配了一個CallInfo數組, 並用L->base_ci指向該數組第一個元素, 用L->end_ci指向該數組最後一個指針, 用L->size_ci記錄數組當前的大小, L->ci記錄的是當前被調用的閉包的調用信息.
下麵講解一個c閉包的調用的過程:
情景: c 函數
int lua_test(lua_State* L){
int a = lua_tonumber(L, 1);
int b = lua_tonumber(L, 2);
a = a + b;
lua_pushnumber(L, a);
}
已經註冊到了lua 中, 形成了一個C閉包, 起名為"test", 下麵去調用它
luaL_dostring(L, "c = test(3, 4)")
調用過程堆棧變化情況如下:
1.初始棧
2.壓入了函數和參數的堆棧
lua_getglobal(L, “test”)
lua_pushnumber(L, 3)
lua_pushnumber(L, 4)
3.調用lua_test開始時的堆棧 lua_call(L,3, 4)
4.調用結束的堆棧
- 取出結果的棧 lua_setglobal(L, “c”)
lua_call函數的過程
- lua具有很強一致性, 不管是dostring, 還是dofile, 都會形成一個閉包, 也就是說, 閉包是lua中用來組織結構的基本構件, 這個特點使得lua中的結構具有一致性, 是一種簡明而強大的概念.
- 根據1, a = test(3, 4)其實是被組織成為一個閉包放在lua棧頂[方便期間, 給這個lua閉包起名為bb], 也就說dostring真正調用的是bb閉包, 然後bb閉包執行時才調用的是test[保存當前信息到當前函數的CallInfo中]
- 在調用test的時刻, L->ci記載著bb閉包的調用信息, 所以, 先把下一個要執行的指令放在L->ci->savedpc中, 以供從test返回後繼續執行.
- 取棧上的test C閉包 cl, 用 cl->isC == 1斷定它的確是一個C閉包[進入一個新的CallInfo, 佈置堆棧]
從L中新分配一個CallInfo ci來記錄test的調用信息, 並把它的值設置到L->ci, 這表明一個新的函數調用開始了, 這裡還要指定test在棧中的位置, L->base = ci->base = ci->func+1, 註意, 這幾個賦值很重要, 導致的堆棧狀態由圖2轉化到圖3, 從圖中可以看出, L->base指向了第一個參數, ci->base也指向了第一個參數, 所以在test中, 我們調用lua_gettop函數返回的值就是2, 因為在調用它的時候, 它的棧幀上只有2個元素, 實現了lua向c語言中傳參數.
[調用實際的函數]安排好堆棧, 下麵就是根據L->ci->func指向的棧上的閉包(及test的C閉包), 找到對應的cl->c->f, 並調用, 就進入了c函數lua_test [獲取返回值調整堆棧, 返回原來的CallInfo]
根據lua_test的返回值, 把test閉包和參數彈出棧, 並把返回值壓入並調整L->top
恢復 L->base, L->ci 和 L->savedpc, 繼續執行.
調用一個新的閉包時:
- 保存當前信息到當前函數的CallInfo中 (CallInfo函數調用的狀態信息)
- 進入一個新的CallInfo, 佈置堆棧
- 調用實際的函數
- 獲取返回值調整堆棧, 返回原來的CallInfo