关于lua中userdata的理解

@[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过后,被清除掉了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LuaUserdata是一种特殊类型的变量,可以存储C/C++编写的数据结构。由于Userdata是由C/C++编写的,因此不能直接在Lua进行遍历,但是可以通过添加元方法来实现遍历操作。 在C/C++,可以通过向Userdata添加元表(Metatable)来定义遍历操作。首先,需要在C/C++编写一个函数,用于获取Userdata的数据结构。然后,将该函数与__ipairs元方法关联,以实现Userdata的遍历。 以下是一个示例的C代码片段,在该代码定义了一个用于遍历Userdata的元方法: ```c typedef struct { int data[10]; int length; } UserData; int get_data(lua_State *L) { UserData *ud = (UserData *)lua_touserdata(L, 1); int index = luaL_checkinteger(L, 2); if (index >= 1 && index <= ud->length) { lua_pushinteger(L, ud->data[index - 1]); return 1; } return 0; } int userdata_pairs(lua_State *L) { lua_pushcfunction(L, get_data); lua_pushvalue(L, 1); lua_pushinteger(L, 0); return 3; } int luaopen_userdata(lua_State *L) { // 创建userdata类型 luaL_newmetatable(L, "userdata"); // 设置__pairs元方法 lua_pushcfunction(L, userdata_pairs); lua_setfield(L, -2, "__pairs"); lua_pushvalue(L, -1); lua_setfield(L, -2, "__index"); return 1; } ``` 在Lua,使用上述定义的Userdata类型和元方法进行遍历操作的示例代码如下: ```lua local userdata = require("userdata") local ud = userdata.new() ud:push(1) ud:push(2) ud:push(3) ud:push(4) ud:push(5) for i, v in ipairs(ud) do print(i, v) end ``` 输出结果: ``` 1 1 2 2 3 3 4 4 5 5 ``` 上述示例,通过添加__pairs元方法,用户可以使用ipairs遍历Userdata的元素。在每次迭代时,调用get_data函数获取userdata指定位置的元素。 需要注意的是,Userdata的具体实现和元方法的定义根据实际需求可能会有所不同,上述示例仅供参考。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值