Lua 呼叫 C 函數時,C 函數若需在多次呼叫間保持狀態 (計數器、快取、配置),不能使用 C 靜態變數 (多執行緒不安全、狀態隨 Lua State 隔離)。Lua 提供三種機制:Registry (全域)、Environment (模組級)、Upvalue (函數級),作用域層層遞減,安全性層層遞增。
| 機制 | 偽索引 | 作用域 | 生命週期 | 適用場景 | 執行緒安全 |
|---|---|---|---|---|---|
| Registry | LUA_REGISTRYINDEX | 全域 (同一 lua_State) | State 存活期間 | 跨模組共享資料、單例、全域快取 | 需自行鎖定 |
| Environment | LUA_ENVIRONINDEX (5.1+) | 模組 (同環境的函數群) | State 存活期間 | 模組私有狀態、配置表、共享資源池 | 需自行鎖定 |
| Upvalue | lua_upvalueindex(i) | 單一 C Closure | Closure 存活期間 | 函數私有狀態、計數器、迭代器狀態 | 每個 Closure 獨立,天然隔離 |
類似 Lua 全域表 _G,但僅 C 可見,鍵值可為任意型別 (除 nil)。
// C 端
void registry_test(lua_State *L) {
lua_pushstring(L, "Hello");
lua_setfield(L, LUA_REGISTRYINDEX, "my_key"); // 存入
lua_getfield(L, LUA_REGISTRYINDEX, "my_key"); // 取出
printf("%s\n", lua_tostring(L, -1)); // Hello
lua_pop(L, 1);
}
// Lua 端無法直接存取 Registry
慣例:鍵名使用 "_MODULENAME_KEY" 前綴避免衝突。Lua 5.4+ 建議用 lua_rawgetp/lua_rawsetp 以 void* 指標為鍵,完全避免字串衝突。
每個 C 函數擁有專屬環境表 (Lua 5.1+),模組開啟時建立共享環境,所有模組函數繼承。
// 模組開啟函數
int luaopen_mymod(lua_State *L) {
lua_newtable(L); // 建立環境表
lua_replace(L, LUA_ENVIRONINDEX); // 設為當前環境
static const luaL_Reg funcs[] = {
{"set", l_set}, {"get", l_get}, {NULL, NULL}
};
luaL_setfuncs(L, funcs, 0); // 註冊函數,自動綁定當前環境
return 1;
}
// 存取環境表
int l_set(lua_State *L) {
luaL_checkstring(L, 1);
lua_pushvalue(L, 1); // 複製值
lua_setfield(L, LUA_ENVIRONINDEX, "data"); // 存入環境
return 0;
}
int l_get(lua_State *L) {
lua_getfield(L, LUA_ENVIRONINDEX, "data"); // 從環境取出
return 1;
}
Lua 端使用:
local m = require "mymod"
m.set("Hello")
print(m.get()) --> Hello
類似 Lua Closure 的上值,每個 C Closure 可攜帶多個 Upvalue,完全隔離,適合計數器、迭代器、狀態機。
// 計數器 Upvalue (索引 1)
static int counter(lua_State *L) {
int val = lua_tointeger(L, lua_upvalueindex(1)); // 讀取
lua_pushinteger(L, ++val); // 推入新值
lua_pushvalue(L, -1); // 複製新值
lua_replace(L, lua_upvalueindex(1)); // 更新 upvalue (彈出複製)
return 1; // 返回新值
}
// 工廠函數:建立獨立計數器
static int new_counter(lua_State *L) {
lua_pushinteger(L, 0); // 初始 upvalue
lua_pushcclosure(L, counter, 1); // 建立 closure 攜帶 1 個 upvalue
return 1;
}
static const luaL_Reg funcs[] = {
{"counter", counter}, // 直接使用共享計數器 (單例)
{"newCounter", new_counter}, // 工廠模式
{NULL, NULL}
};
Lua 端測試:
local m = require "mymod"
local f1 = m.newCounter()
print(f1()) --> 1
print(f1()) --> 2
local f2 = m.newCounter()
print(f2()) --> 1 (獨立狀態)
print(f1()) --> 3
string.dump 不保存 upvalue,跨進程傳遞需自行方案lua_setfenv (5.1) / lua_setupvalue (5.2+) 可動態切換lua_pushlightuserdata(L, ptr) + lua_rawsetp(L, LUA_REGISTRYINDEX, ptr),生命週期自管lua_State 獨立 Registry/Environment,跨執行緒共享需用 lua_xmove 或外部同步lua_replace(L, idx) 不彈出棧頂 —— 實際會彈出lua_upvalueindex(1) 從 1 開始,非 0lua_replace(L, LUA_ENVIRONINDEX) 導致函數綁定錯誤環境掌握三種狀態機制的作用域邊界、生命週期、封裝性差異,能編寫出模組化、執行緒友善、易於測試的 Lua C 擴充模組。Upvalue 應為首選,Environment 次之,Registry 為最後手段。