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

关于lua中userdata的理解相关推荐

  1. 对Lua中Userdata的理解

    目录 一.概述 二.源码实现 相关函数实现 1,lua_pushlightuserdata 2,lua_newuserdata 3,luaC_newobj(lgc.c) 4,createstrobj( ...

  2. lua的userdata解析

    原文  http://www.jellythink.com/archives/587 话从这里说起 在我发表<Lua中的类型与值>这篇文章时,就有读者给我留言了,说:你应该好好总结一下Lu ...

  3. lua把userdata写入mysql_Lua教程(十九):userdata

    在Lua中可以通过自定义类型的方式与C语言代码更高效.更灵活的交互.这里我们通过一个简单完整的示例来学习一下Lua中userdata的使用方式.需要说明的是,该示例完全来自于Programming i ...

  4. lua把userdata写入mysql_Lua 之 userdata

    Lua 之 userdata 在Lua中可以通过自定义类型(user data)与C语言代码更高效.更灵活的交互,从而扩展Lua能够表达的类型. full userdata full userdata ...

  5. lua中的自定义类型:userdata

    本节是对lua中的userdata的一个小小总结,其示例参考自<Lua程序设计>(第四版) 问题:在lua中使用布尔数组. 虽然lua中可以使用表(table)来实现布尔数组,但是其空间利 ...

  6. 两个函数彻底理解Lua中的闭包

    本文通过两个函数彻底搞懂Lua中的闭包,相信看完这两个函数,应该能理解什么是Lua闭包.废话不多说,上 code: 1 --[[************************************ ...

  7. 理解lua中的metatable和__index

    Lua 学习笔记--metatable和__index 版权声明:本文为博主 Fisher1006 原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:ht ...

  8. Lua中的userdata

    userdata 是一种用户自定义数据,用于表示一种由应用程序或 C/C++ 语言库所创建的类型,可以将任意 C/C++ 的任意数据类型的数据(通常是 struct .指针和类)存储到 Lua 变量中 ...

  9. lua中给userdata绑定元表示例

    前言 这篇博客,我估计写不好.一方面是内容挺绕,一方面是我没有看过书,是照葫芦画瓢写代码. 前置要求: Lua调用C代码 lua中表与元表 Lua操作C语言用户自定义类型数据Userdata 上面第三 ...

最新文章

  1. 测试晶面间距软件_【干货】高分辨TEM晶面间距的测量与标定丨DM软件
  2. Asp.net中的Cache--HttpRuntim.Cache 和 HttpContext.Current.Cache
  3. 永洪Desktop全能力永久免费 国产数据分析工具迈向新阶段
  4. Go gin获取GET请求参数
  5. c++算术溢出_二进制安全之堆溢出(系列)——CTF环境配置
  6. 《机器学习实战》第十三章 PCA
  7. boost::hash_combine模块实现json哈希值的测试程序
  8. Codeforces 658D Bear and Polynomials【数学】
  9. dotnet 手工打一个 dotnet tool 包
  10. 专题导读:大数据整理
  11. SpringMVC 异步交互 AJAX 文件上传
  12. markDown用这一招实现图片并排显示
  13. IE成长之路--OSPF报文类型
  14. 【5年Android从零复盘系列之二十八】Android存储(3):assets文件详解
  15. 四川企立方电商:拼多多降价导致降权怎么
  16. 外设测试 - FAN 接口测试
  17. Word怎样设置处理表格与表格标题之间的间距问题
  18. 主機名稱控制者: DNS 伺服器
  19. 2022-2028年中国珠宝电子商务行业市场需求分析及投资方向研究报告
  20. linux下运行omnet,Linux中安装OMNeT++过程

热门文章

  1. 大数据学习-大数据环境配置
  2. Camera对焦的几种方式
  3. 女生转行IT和汉子要考虑的问题有什么不一样?
  4. leetcode算法之二分查找
  5. js用for循环做九九乘法表
  6. 用”钉钉“做班级直播(快速操作指南)
  7. 2020国赛E分析:校园供水系统智能管理
  8. 教你取消Mac上红色通知标记,强迫症朋友的福利!
  9. 各种气候数据的下载(以下载青岛地区40年间月平均气温数据的下载为例)【转】...
  10. 暴力破解———罗马数字逆向解法,猜年龄利用位数信息,罗马数字的枚举解法