userdata 是 Lua 與 C/C++ 交互的核心機制:它是一塊由 Lua GC 管理的原始記憶體,配合 Metatable 可模擬面向對象行為。本筆記記錄從零構建一個 LuaArray (float 陣列) 的完整過程。
| 模式 | 記憶體歸屬 | GC 行為 | 適用場景 |
|---|---|---|---|
| 全托管 | 對象數據直接放在 userdata 內 | Lua GC 回收 userdata 時自動銷毀對象 | 小型、生命週期綁定 Lua 的對象 |
| 半托管 | userdata 僅存 指標,對象在 C++ 堆上 | 需在 __gc 中手動 delete | 大型對象、需共享給多 Lua 狀態、或 C++ 端長期持有 |
本文演示全托管模式。
struct LuaArray {
int size;
float data[1]; // 柔性數組成員 (C99),實際分配時多分配 size*sizeof(float)
};
static const char* LuaArrayTableName = "LuaArray";
static const luaL_Reg ArrayFuncs[] = {
{"__newindex", SetArrayValue},
{"__len", GetArrayLength},
{"__gc", ReleaseArray},
{"get", GetArrayValue},
{"sum", SumArray},
{"new", NewArray},
{NULL, NULL}
};
void InitArray(lua_State* L) {
luaL_register(L, LuaArrayTableName, ArrayFuncs); // 創建表並註冊函數
lua_pushvalue(L, -1); // 複製表
lua_setfield(L, -2, "__index"); // 表.__index = 表 (自身)
lua_pop(L, 1); // 彈出表,留在 registry 中可通过全局名訪問
}
關鍵點: __index = 自身 使得 arr:get(1)、arr.sum() 等方法調用能在 Metatable 中找到函數;同時 __newindex 支援 arr[1] = 100 語法糖。
int NewArray(lua_State* L) {
int n = luaL_checkint(L, 1); // 參數 1:元素個數
size_t memsize = sizeof(LuaArray) + n * sizeof(float);
LuaArray* arr = (LuaArray*)lua_newuserdata(L, memsize); // 分配 userdata
arr->size = n;
for (int i = 0; i <= n; ++i) arr->data[i] = 0.0f; // 初始化
lua_getglobal(L, LuaArrayTableName); // 取得 Metatable
lua_setmetatable(L, -2); // 綁定到 userdata
return 1; // 返回 userdata
}
Lua 調用:arr = LuaArray.new(10)。
int SetArrayValue(lua_State* L) {
LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
int idx = luaL_checkint(L, 2);
float val = (float)luaL_checknumber(L, 3);
arr->data[idx] = val; // 直接寫入 userdata 記憶體
return 0;
}
int GetArrayValue(lua_State* L) {
LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
int idx = luaL_checkint(L, 2);
lua_pushnumber(L, arr->data[idx]);
return 1;
}
Lua 用法:
arr[1] = 100 -- 觸發 __newindex
print(arr:get(1)) -- 調用 get 方法 (因 __index 指向自身表)
int GetArrayLength(lua_State* L) {
LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
lua_pushinteger(L, arr->size);
return 1;
}
int SumArray(lua_State* L) {
LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
float sum = 0;
for (int i = 0; i < arr->size; ++i) sum += arr->data[i];
lua_pushnumber(L, sum);
return 1;
}
int ReleaseArray(lua_State* L) {
LuaArray* arr = (LuaArray*)lua_touserdata(L, 1);
// 全托管模式下,userdata 記憶體由 Lua 自動釋放,這裡通常無需操作
// 若在 userdata 中藏有額外 new 出的指針(演示用),需手動 delete
float* extra;
memcpy(&extra, arr->data, sizeof(float*)); // 假設藏在 data[0]
delete[] extra;
return 0;
}
Lua 端:arr = nil; collectgarbage() 觸發 __gc。
arr = LuaArray.new(10)
arr[1] = 100
arr[2] = 200
print("len:", #arr) -- __len
print("get(1):", arr:get(1))
print("sum:", arr:sum())
arr = nil
collectgarbage() -- 觸發 __gc
print("collected")
lua_touserdata 前用 luaL_checkudata(L, 1, LuaArrayTableName) 確保傳入的是正確 userdata,否則報錯。__index 指向基類表,實現單繼承。T*,__gc 執行 delete static_cast(ptr) ,C++ 端可繼續持有對象。lua_pushlightuserdata 不受 GC 管理,僅作指標傳遞,慎用。userdata + metatable 是 Lua 擴展 C/C++ 類型的標準範式:
__index, __newindex, __gc, __len...)lua_touserdata 取回指針操作數據掌握此機制,即可將任意 C/C++ 庫無縫暴露給 Lua 腳本層。