@[TOC] 关于lua中userdata的理解
关于Userdata的理解
Userdata数据结构
- userdata是用来存放用户自定义数据结构的实列,userdata的类型有两种类型,分别是lightuserdata,和fulluserdata。lightuserdata是value结构中的一个变量类型,本质上是一个void*指针:
// luaobject.h
typedef union lua_Value{
struct GCObject* gc;
void* p;
int b;
lua_Integer i;
lua_Number n;
lua_CFunction f;
} Value;
light userdata的内存,需要用户自行管理,而fulldata则是通过lua的gc机制进行管理,本章主要讲fulluserdata,所有的userdata均为full userdata。
对full userdata的操作实现,一般在C层进行的,后面会有例子
userdata的数据结,在dummylua中,定义如下所示:
//luaobject.h
#define CommonHeader struct GCObject* next ;lua_byte tt_ ;lu_byte marked
typedef struct Udata {
CommonHeader; //GC公共头部
struct Table* metatable; //userdata 可以设置metatable,一般用于设置_gc域,在userdata被回收之前,
//__gc函数会被调用,一般用于回收系统资源
int ttuv_; //相当于Tvalue 的tt_,用来指代user_变量的类型
int len; // 自定义域的大小
Value user_; //本质就是Tvalue的value_的value部分,这里将TValue拆分成了两个部分
}Udata;
UserData的接口
分别是Lua_newuserdata.getudatamem,setuservalue.getuservalue。
luaS_newuserdata的定义:
//luastring.c
udata* luaS_newuserdata(struct lua_State* L,int size);
这个接口,就是创建了一个userdata实例,这个userdata实例的大小就是Udata头部+传入的size大小,我们可以看一下实例的构成:
±-------------------±------------------+
|Header:Udata | user domain |
±-------------------±-----------------+
前面的Header,就是sizeof(Udata)的大小,而后面的user domain,则是luaS_newuserdata的第二个参数size来指定,比方,现在我们定义了一个Vector3的数据结构:
typedef struct Vector3 {
float x;
float y;
float z;
}Vector3;
要创建时,这个Vector3 关联的userdata。那么它的创建代码则如下所示:
Udata* u = luaS_newuserdata(L,sizeof(Vector3));
第二个参数,size的大小则是sizeof(vector3)的大小,也就是12,那么此时,user domain的大小则为12byte的大小。
在完成了userdata实例的创建之后,要在C层获取我们自定义的结构实例的指针,此时需要一个接口getudatamem.
#define getudatamem(o) (cast(char*,o)+sizeof(Udata))
这个宏的作用在。则是将userdata实例中,user domain部分的指针拿到,获取的结果所指向的位置,如下’ ^ '所示:
±-------------------±------------------+
|Header:Udata | user domain |
±-------------------±-----------------+
^
通过这个宏,我们可以获取结构自定义结构变量的实例,具体方法如下所示:
Vector3* v3 = (vector3*)getudatamem(u);
接下来,就可以对V3所指向的内存块进行对应的处理了.
userdata 还有一个重要的接口,就是setuserdata,这是一个宏,它的作用是将一个Tvalue实例赋值到Udata头部中,分别将TValue实例的tt__赋值给Udata的ttuv_,将TValue的value_赋值给Udata的user_字段中,其定义如下所示:
//luaobject.h
#define setuservalue(u ,o)\
(u) ->ttuv_ = (O) ->tt_;(u) ->user_=(o)->value_
luaobject.h
#define getuservalue(u, o) \
(o)->tt_ = (u)->ttuv_; (o)->value_ = (u)->user_
Userdata的gc处理
关于lua的gc机制,主要是userdata的标记和清楚阶段的逻辑处理。
userdata在标记阶段,其逻辑如下:
// luagc.c
void reallymarkobject(struct lua_State* L, struct GCObject* gco) {
struct global_State* g = G(L);
white2gray(gco);
switch(gco->tt_) {
case LUA_TTHREAD:{
linkgclist(gco2th(gco), g->gray);
} break;
case LUA_TTABLE:{
linkgclist(gco2tbl(gco), g->gray);
} break;
case LUA_TLCL:{
linkgclist(gco2lclosure(gco), g->gray);
} break;
case LUA_TCCL:{
linkgclist(gco2cclosure(gco), g->gray);
} break;
case LUA_TPROTO:{
linkgclist(gco2proto(gco), g->gray);
} break;
case LUA_SHRSTR:{
gray2black(gco);
struct TString* ts = gco2ts(gco);
g->GCmemtrav += sizelstring(ts->shrlen);
} break;
case LUA_LNGSTR:{
gray2black(gco);
struct TString* ts = gco2ts(gco);
g->GCmemtrav += sizelstring(ts->u.lnglen);
} break;
case LUA_TUSERDATA: {
gray2black(gco);
TValue uvalue;
Udata* u = gco2u(gco);
getuservalue(u, &uvalue);
if (u->metatable) {
markobject(L, u->metatable);
}
if (iscollectable(&uvalue) && iswhite(gcvalue(&uvalue))) {
reallymarkobject(L, gcvalue(&uvalue));
}
g->GCmemtrav += sizeof(Udata);
g->GCmemtrav += u->len;
} break;
default:break;
}
}
我们已知了,luaS_newuserdata接口,其实是在luastring模块里的,因为它的创建和使用逻辑和luastring非常类似,在标记阶段也是,userdata实例本身,在标记为灰色后,直接标记成黑色,并且将它的metatable(如果存在的话)标记为灰色,并且放入gray列表中,此外如果userdata的user域存在,且是一个gc实例,那么它也需要被标记为灰色,并且放入gray列表中。这里,我们需要注意几个问题,一个就是userdata在标记扫描阶段,直接整个被标记为黑色,并不会对user domain内部的任何域进行检查和处理。
接下来,我们来看一下userdata的清除逻辑,如下所示:
// luagc.c
static lu_mem freeobj(struct lua_State* L, struct GCObject* gco) {
switch(gco->tt_) {
case LUA_SHRSTR: {
struct TString* ts = gco2ts(gco);
luaS_remove(L, ts);
lu_mem sz = sizelstring(ts->shrlen);
luaM_free(L, ts, sz);
return sz;
} break;
case LUA_LNGSTR: {
struct TString* ts = gco2ts(gco);
lu_mem sz = sizelstring(ts->u.lnglen);
luaM_free(L, ts, sz);
} break;
case LUA_TTABLE: {
struct Table* tbl = gco2tbl(gco);
lu_mem sz = sizeof(struct Table) + tbl->arraysize * sizeof(TValue) + twoto(tbl->lsizenode) * sizeof(Node);
luaH_free(L, tbl);
return sz;
} break;
case LUA_TTHREAD: {
// TODO
} break;
case LUA_TLCL: {
struct LClosure* cl = gco2lclosure(gco);
lu_mem sz = sizeof(LClosure);
luaF_freeLclosure(L, cl);
return sz;
} break;
case LUA_TCCL: {
struct CClosure* cc = gco2cclosure(gco);
lu_mem sz = sizeof(struct CClosure);
luaF_freeCclosure(L, cc);
return sz;
} break;
case LUA_TPROTO: {
struct Proto* f = gco2proto(gco);
lu_mem sz = luaF_sizeproto(L, f);
luaF_freeproto(L, f);
return sz;
} break;
case LUA_TUSERDATA: {
Udata* u = gco2u(gco);
lu_mem sz = sizeof(Udata) + u->len;
luaM_free(L, u, sz);
return sz;
} break;
default:{
lua_assert(0);
} break;
}
return 0;
}
我们可以看case LUA_TUSERDATA的那部分逻辑,可以看到的是,userdata实例是整个被释放掉,未对userdata内部的user domian部分做任何的处理,也就是说如果user domain内部包含了堆内存实例的指针,这部分需要用户自己进行处理。
userdata的user domain域内部的堆内存清理
前面,我们提到了,user domain域内,如果包含了指向堆内存的指针,那么这部分需要我们进行处理,需要怎么处理呢?lua的清除逻辑,并没有提供这样的机会,但是,我们前面说过,userdata有一个metatable域,为userdata设置一个metatable,并且这个metatable如果包含一个名为__gc的函数,那么在userdata被gc回收之前,会首先调用这个函数,我们来看一个伪代码,假设userdata的metatable是如下所示:
{
__gc = function(udata) release(udata) end
}
那么在udata实例,被gc回收之前,上面这个__gc函数会被调用,该函数的参数,就是userdata实例本身,release函数是用户自己在c层实现的函数,导出给lua层使用的,这个release函数,将在c层逻辑中,对udata的user domain域中,包含的堆内存实例进行释放操作,避免内存泄露。
userdata的使用例子
这个测试用例创建了一个userdata实例,并且为它设置了一个包含_ _gc函数的metatable,最后将这个userdata实例,从栈中移除,接着调用了fullgc函数,最后显示的结果是,这个__gc函数被调用。
// p10_test.c
#include "p10_test.h"
#include "../common/luastring.h"
#include "../vm/luagc.h"
#include "../common/luatable.h"
typedef struct Vector3 {
float x;
float y;
float z;
} Vector3;
int gcfunc(struct lua_State* L) {
Udata* u = lua_touserdata(L, -1);
Vector3* v3 = (Vector3*)getudatamem(u);
printf("total_size:%d x:%f, y:%f, z:%f", u->len, v3->x, v3->y, v3->z);
return 0;
}
void test_create_object(struct lua_State* L) {
Udata* u = luaS_newuserdata(L, sizeof(Vector3));
Vector3* v3 = (Vector3*)getudatamem(u);
v3->x = 10.0f;
v3->y = 10.0f;
v3->z = 10.0f;
L->top->tt_ = LUA_TUSERDATA;
L->top->value_.gc = obj2gco(u);
increase_top(L);
struct Table* t = luaH_new(L);
struct GCObject* gco = obj2gco(t);
TValue tv;
tv.tt_ = LUA_TTABLE;
tv.value_.gc = gco;
setobj(L->top, &tv);
increase_top(L);
lua_pushCclosure(L, gcfunc, 0);
lua_setfield(L, -2, "__gc");
lua_setmetatable(L, -2);
L->top--;
return;
}
void p10_test_main() {
struct lua_State* L = luaL_newstate();
luaL_openlibs(L);
test_create_object(L);
luaC_fullgc(L);
luaL_close(L);
}
执行后的结果为:
total_size:12 x:10.0, y:10.0, z:10.0
这说明,创建出来的userdata,在fullgc过后,被清除掉了。