這些東西是平時遇到的, 以爲有必定的價值, 因此記錄下來, 之後遇到相似的問題能夠查閱, 同時分享出來也能方便須要的人, 轉載請註明來自RingOfTheC[ring.of.the.c@gmail.com]javascript
這裏, 簡單的記錄一下lua中閉包的知識和C閉包調用java
前提知識: 在lua api小記2中已經分析了lua中值的結構, 是一個 TValue{value, tt}組合, 若是有疑問, 能夠去看一下c++
lua中有兩種閉包, c閉包和lua閉包程序員
兩種閉包的公共部分:api
#define ClosureHeader CommonHeader; lu_byte isC; lua_byte nupvalues; GCObject* gclist; struct Table env數組
/*是不是C閉包*/ /*upval的個數*/ /* 閉包的env, set/getenv就是操縱的它 */數據結構
C閉包的結構閉包
struct CClosure{函數
ClosureHeader;post
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;
}
爲何你們叫閉包, 不叫它函數, 它看起來就是函數啊? 爲何要發明一個"閉包"這麼一個聽起來蛋疼的詞呢? 我也糾結在這裏很久了, 大概快一年半了吧~~~=.=我比較笨~~~隨着看源碼, 如今想通了, 拿出一些的本身在研究過程當中的心得[儘可能的通俗易懂]:
1. c 語言中的函數的定義: 對功能的抽象塊, 這個你們沒什麼異議吧.
2. lua對函數作了擴展:
a. 能夠把幾個值和函數綁定在一塊兒, 這些值被稱爲upvalue.
ps: 可能有人以爲c++的函數對象也能夠把幾個值和函數綁定起來啊, 是這樣的, 可是這個問題就像是"在彙編中也能夠實現面向對象呀"同樣, lua從語言層面對upvalue提供了支持, 就像c++/java從語言層面提供了對類, 對象的支持同樣, 固然大大的解放了咱們程序員的工做量, 並且配上lua動態類型, 更是讓人輕鬆了很多.
b. 每一個函數能夠和一個env(環境)綁定.
ps: 若是說上面的upvalue還能在c++中coding出來, 那麼env 上下文環境這種動態語言中特有的東西c++就沒有明顯的對應結構了吧? 可能有人以爲lua是c寫的, 經過coding也能夠實現, 好吧=.= , "能作和作"是兩碼事, 就想你能步行從北京到上海, 不代表你就必需要這麼作. env是很是重要和有用的東西, 它能夠輕鬆創造出一個受限的環境, 就是傳說中的"沙盒", 我說的更通俗一點就是"一個動態名字空間機制". 這個先暫時不分析.
好了, 如今咱們看到
c 函數 { 功能抽象 }
lua 閉包 {功能抽象, upvalue, env}
重點: 閉包 == {功能抽象, upvalue, env}
看到這裏, 你們都明白了, 若是把lua中的{功能抽象, upvalue, env}也稱爲函數, 不但容易引發你們的誤解覺得它就是和c函數同樣, 並且它確實不能很好的表達出lua函數的豐富內涵, 閉包, "閉" 是指的它是一個object, 一個看得見摸得着的東西, 不可分割的總體(first class); "包" 指的是它包含了功能抽象, upvalue, env. 這 裏一個頗有趣的事實就是, {功能抽象, upvalue, env}是不少動態語言的一個實現特徵, 好比lua, javascript都有實現這樣的結構, 它是先被實現出來, 而後冠以"閉包"這樣一個名稱. 因此, 你單單想去理解閉包這個詞的話, 基本是沒有辦法理解的, 去網上查閉包, 沒用, 你能查到的就是幾個用閉包舉出的例子, 看完之後保證你的感受是"這玩意挺神祕的, 可是仍是不懂什麼是閉包", 爲何不懂? 由於它指的是一種實現結構特徵, 是爲了實現動態語言中的函數first class和上下文概念而創造出來的.
寧肯多說幾句, 只要對加深理解有好處就行, 有這樣兩個個句子"我騎車去買點水果" "我用來閉包{功能抽象, upvalue, env}實現動態語言中的函數first class和上下文概念" , 閉包和"騎車"都是你達到目地的一種手段, 爲了買水果你纔想了"騎車"這樣一個主意, 並非爲了騎車而去買水果. 只把把眼睛盯在騎車上是不對的, 它只是手段.
流程: 1. 建立一個 sizeof(CClosure) + (n - 1) * sizeof(TValue)大小的內存, 這段內存是 CClosure + TValue[n], 並作gc簿記[這點過重要了, 爲何lua要控制本身世界中的全部變量, 就是由於它要作gc簿記來管理內存], isC= 1 標示其是一個C閉包.
2. c->f = f綁定c函數. --------- 閉包.功能抽象 = f
3. env = 當前閉包的env[這說明了被建立的閉包繼承了建立它的閉包的環境]. ----------- 閉包.env = env
4. 把棧上的n個元素賦值到c->upvalue[]數組中, 順序是越先入棧的值放在upvalue數組的越開始位置, c->nupvalues指定改閉包upvalue的個數. ---------- 閉包.upvalue = upvalue
5. 彈出棧上n個元素, 並壓入新建的Closure到棧頂.
整個流程是比較簡單的, 分配內存, 填寫屬性, 鏈入gc監控, 綁定c函數, 綁定upvalue, 綁定env一個C閉包就ok了, 請結合上面給的閉包的解釋, 很清楚了.
lua 閉包調用信息結構:
struct CallInfo {
StkId base; /* base for this function */ ---- 閉包調用的棧基
StkId func; /* function index in the stack */ ---- 要調用的閉包在棧上的位置
StkId top; /* top for this function */ ---- 閉包的棧使用限制, 就是lua_push*的時候得看着點, push太多就超了, 能夠lua_checkstack來擴
const Instruction *savedpc; ---- 若是在本閉包中再次調用別的閉包, 那麼該值就保存下一條指令以便在返回時繼續執行
int nresults; /* expected number of results from this function */ ---- 閉包要返回的值個數
int tailcalls; /* number of tail calls lost under this entry */ ---- 尾遞歸用, 暫時無論
}
從註釋就能夠看出來, 這個結構是比較簡單的, 它的做用就是維護一個函數調用的有關信息, 其實和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. 首先, 咱們把它翻譯成對應的c api
1. 最初的堆棧
lua_getglobal(L, 「test」)
lua_pushnumber(L, 3)
lua_pushnumber(L, 4)
2. 壓入了函數和參數的堆棧
lua_call(L, 2, 1)
3. 調用lua_test開始時的堆棧
4. 調用結束的堆棧
lua_setglobal(L, 「c」)
5. 取出調用結果的堆棧
咱們重點想要知道的是lua_call函數的過程
1. lua的一致性在這裏再一次的讓人震撼, 不論是dostring, 仍是dofile, 都會造成一個閉包, 也就是說, 閉包是lua中用來組織結構的基本構件, 這個特色使得lua中的結構具備一致性, 是一種簡明而強大的概念.
2. 根據1, a = test(3, 4)實際上是被組織成爲一個閉包放在lua棧頂[方便期間, 給這個lua閉包起名爲bb], 也就說dostring真正調用的是bb閉包, 而後bb閉包執行時才調用的是test
[保存當前信息到當前函數的CallInfo中]
3. 在調用test的時刻, L->ci記載着bb閉包的調用信息, 因此, 先把下一個要執行的指令放在L->ci->savedpc中, 以供從test返回後繼續執行.
4. 取棧上的test C閉包 cl, 用 cl->isC == 1判定它的確是一個C閉包
[進入一個新的CallInfo, 佈置堆棧]
5. 從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語言中傳參數.
[調用實際的函數]
6. 安排好堆棧, 下面就是根據L->ci->func指向的棧上的閉包(及test的C閉包), 找到對應的cl->c->f, 並調用, 就進入了c函數lua_test
[獲取返回值調整堆棧, 返回原來的CallInfo]
7. 根據lua_test的返回值, 把test閉包和參數彈出棧, 並把返回值壓入並調整L->top
8. 恢復 L->base, L->ci 和 L->savedpc, 繼續執行.
總結: 調用一個新的閉包時 1. 保存當前信息到當前函數的CallInfo中 2. 進入一個新的CallInfo, 佈置堆棧 3. 調用實際的函數 4. 獲取返回值調整堆棧, 返回原來的CallInfo
短短續續一共寫了6個小時......... 先寫到這裏吧, 對了, 我是開始寫blog不久, 並且表達能力有限, 你們若是要看的話取其精華, 捨棄糟粕吧:)