閉包是Lua語言編程一個重要而又常用的概念。它主要作用是在函數離開作用域後還可以訪問外部的臨時變量,這些變量稱爲upvalue。
閉包分爲兩種,分別CClosure和LClosure。它們都被封裝到一個Closure結構體裏,CClosure和LClosure都有一個ClosureHeader的結構體。結構體ClosureHeader的字段作用:
1.isC: 區分是哪一種閉包類型。0是LClosure, 1是CClosure
2.nupvalues:記錄upvalue的數量。
#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;
這裏我們主要分析LClosure的情況。例如以下創建閉包的代碼
function test()
local a = 1
local function callback()
print(a)
end
return callback
end
local cb = test()
cb()
在調用test函數時,創建一個local變量和一個callback函數,callback可以訪問外部的a。當調用test的時候,此時棧上會創建a,然後創建callback這個閉包。
創建閉包的過程:
1.獲取函數開始到閉包創建前的upvalue數量,這裏只有a,數量爲1.
2.創建閉包會根據upvalue的數量來創建,upvalue的數量會影響閉包的創建大小。
3.閉包LClosure創建好了之後,需要去創建UpValue,即閉包內可以訪問的外部變量,UpValue會和openvalue鏈成一條雙向鏈表,此時LuaState的openvalue會指向最新的UpValue,UpValue爲open 狀態。open 狀態指的是:upvalue的值還在棧裏,當閉包要從棧裏pop掉的時候,UpValue會變成close狀態(稍後再說)。
4.創建閉包的時候,這個時候內存圖爲:
假如現在test函數執行完畢,返回callback函數,這時候棧會把LClosure Pop出來。Pop出來的時候,LClosure指向的UpValue會把指向的值設置到自己的結構體上,並把UpValue指向自己結構TValue上,此時即爲Close狀態。同時UpValue會從openvalue的鏈表上移除。此時的內存圖爲:
閉包和變量從棧中移除後,閉包還能訪問原來的變量,但是它並不在棧上,而是被拷貝閉包一直指向的UpValue去,這就是爲什麼閉包和變量被Pop掉後還能訪問並修改外部變量的原因。