专业汉语词典知识平台,分享汉字词语知识、历史文学知识解答!

励北网
励北网

userdata属性,数据类型之UserData

来源:小易整编  作者:小易  发布时间:2023-03-21 08:51
摘要:userdata属性,数据类型之UserData前言本文将讲解Lua与其它编程语言(例如C语言,C++语言等)交互时常常需要使用的变量类型,分别为UserData和LightUserData,我们日常开发的时候如果只使用Lua一种编程语言,...

userdata属性,数据类型之UserData

前言

本文将讲解Lua与其它编程语言(例如C语言,C++语言等)交互时常常需要使用的变量类型,分别为UserData和LightUserData,我们日常开发的时候如果只使用Lua一种编程语言,则这部分内容平时应该接触得会比较少。

交互:指不同编程语言间可以互相调用。在Lua中,与其它编程语言的调用是利用堆栈完成的,一种编程语言把内容放在栈上(一片内存区域),另一种编程语言根据定义好的数据格式解释栈上的二进制数据内容为本语言的数据结构。

定义

UserData:又有名字叫Full User Data,全量级的用户数据。通常是一块内存区域,可用于转换为特殊的数据结构,UserData内部数据的解析与设置由使用者自己进行实现,Lua并没有能力处理这份二进制内存,Lua只负责这块内存区域的内存管理与回收。

LightUserData: 轻量级用户数据,相比于上面的FullUserData,它是轻量的,它只是一个指针,在C语言中对应的类型为void*,占4或8个字节,Lua也不会管理这个指针的内存,全由使用者自己管理。

本文上半部分会讲解UserData,下半部分会讲解LightUserData。

UserData用例

我们先看UserData类型的使用方式,Lua的源码是用C语言写的,我们在本文中也将选择C语言作为与Lua交互的语言。

我们举个例子开始学习,我们在C语言中定义了如下结构体:

struct People{  string name;  int age;}

为了能在Lua中使用这个结构体,我们这里就可以使用UserData。我们看下面这段C语言代码就是创建了一个UserData对象并插入到Lua堆栈顶部:

static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));     //设置pPeople对象的元表   // ...........(暂时省略)  return 1; // 新的userdata已经在栈上了}

接下来就可以在Lua中创建一个People对象并打印其名字:

local people = People.New()print(people.name)

上面这个例子看着简单,其实我把C语言函数导出,元表设置相关的代码都省略了,本章中我们后面会再详细讲,这里先对与C语言交互简单有个印象就好了。

接下来我们就开始UserData的源码学习吧。

UserData类型定义

见源码《lua.h》,类型枚举为LUA_TUSERDATA,数值为7,二进制8位为0000 0111:

<p>userdata属性,数据类型之UserData</p><p>前言</p><p>本文将讲解Lua与其它编程语言(例如C语言,C++语言等)交互时常常需要使用的变量类型,分别为UserData和LightUserData,我们日常开发的时候如果只使用Lua一种编程语言,则这部分内容平时应该接触得会比较少。</p><p>交互:指不同编程语言间可以互相调用。在Lua中,与其它编程语言的调用是利用堆栈完成的,一种编程语言把内容放在栈上(一片内存区域),另一种编程语言根据定义好的数据格式解释栈上的二进制数据内容为本语言的数据结构。</p><p>定义</p><p>UserData:又有名字叫Full User Data,全量级的用户数据。通常是一块内存区域,可用于转换为特殊的数据结构,UserData内部数据的解析与设置由使用者自己进行实现,Lua并没有能力处理这份二进制内存,Lua只负责这块内存区域的内存管理与回收。</p><p>LightUserData: 轻量级用户数据,相比于上面的FullUserData,它是轻量的,它只是一个指针,在C语言中对应的类型为void*,占4或8个字节,Lua也不会管理这个指针的内存,全由使用者自己管理。</p><p>本文上半部分会讲解UserData,下半部分会讲解LightUserData。</p><p>UserData用例</p><p>我们先看UserData类型的使用方式,Lua的源码是用C语言写的,我们在本文中也将选择C语言作为与Lua交互的语言。</p><p>我们举个例子开始学习,我们在C语言中定义了如下结构体:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>struct People{  string name;  int age;}</p></section><p>为了能在Lua中使用这个结构体,我们这里就可以使用UserData。我们看下面这段C语言代码就是创建了一个UserData对象并插入到Lua堆栈顶部:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));     //设置pPeople对象的元表   // ...........(暂时省略)   return 1; // 新的userdata已经在栈上了}</p></section><p>接下来就可以在Lua中创建一个People对象并打印其名字:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li></ul><p>local people = People.New()print(people.name)</p></section><p>上面这个例子看着简单,其实我把C语言函数导出,元表设置相关的代码都省略了,本章中我们后面会再详细讲,这里先对与C语言交互简单有个印象就好了。</p><p>接下来我们就开始UserData的源码学习吧。</p><p>UserData类型定义</p><p>见源码《lua.h》,类型枚举为LUA_TUSERDATA,数值为7,二进制8位为0000&nbsp;0111:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/89827702be55771.png"/> </p><p>接下来看下图红框1部分,可知USERDATA类型只是单纯声明了一个LUA_VUSERDATA类型变体作为类型别名,没有声明多余的其它类型变体:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/47b11048950fe8c.png"/> </p><p>上图红框2,3表示UserData也可以看作为GCObject的子类,这些类在判断变量是否合法的时候都会使用ctb宏,只有满足ctb宏IS_COLLECTABLE(第7位为1)才是合法的对象。所以一个合法的UserData类型的类型标识(type tag)8位二进制为0100&nbsp;0111。</p><p>红框3中代码val_(o).gc表示UserData数据存于gc指针指向的地址上:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9c6844f55f85161.png"/> </p><p>gco2u(GCObject*)函数则把该指针转换为真正的对象:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>#define gco2u(o)  check_exp((o)-&gt;tt == LUA_VUSERDATA, &amp;((cast_u(o))-&gt;u))</p></section><p>最后返回的为下面Udata u这个数据:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/5001d7ef1e4650f.png"/> </p><p>Udata就是UserData在源码中的具体类型结构,我们下面就来详细学习一下:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9832fe2a46f9e4c.png"/> </p><p>C语言结构体占用内存存储于Udata结构体的末尾,Udata有两种使用形式:一种声明了UValue数组,可以额外存储一组数据,另一种则没有声明,而是直接后面就接上C语言结构体(结构相当于上图中的Udata0结构体),内存结构分别如下图所示,左边红色部分为使用UValue数组所多出来的部分:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0f99c877a29b.png"/> </p><p>Udata的创建代码见源码《lstring.c》luaS_newudata:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/f906693bd60650f.png"/> </p><p>Lua源码中提供了设置与获取UValue数组元素的set与get函数,不用细看:</p><p>源码《lapi.c》lua_setiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/49a1de471aeb.png"/> </p><p>源码《lapi.c》lua_getiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/81311dcce94242d.png"/> </p><p>在极大多数情况下,使用Udata都不需要用到UValue数组,因为C语言结构体也存在于Udata之中(末尾),在这个结构体中完全可以存储所需要的任何数据,不需要非得存储在UValue数组当中,所以我们可以不需要学习这一种Udata的使用方式。可以把Udata直接理解为下图的Udata0来使用:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/17fe6c03072caa4.png"/> </p><p>Udata0仅在计算Udata占用内存空间大小的时候会用于计算,实际源码中不会真正创建出Udata0类型的对象,上图中的bindata即可理解为指向C语言结构体的指针,调用luaS_newudata在C语言中创建一个userdata后,bindata这部分数据在栈顶,C语言中可直接对这部分内存强制转换为对应的C语言结构体对象,如开篇中的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));</p></section><p>知道了怎么创建一个UserData以及其内部结构。我们接下来看下删除是怎么实现的。</p><p>UserData的删除</p><p>非常简单,userdata的删除也跟其它Lua基础类型一样,当没有被引用的时候就会被清除,在C语言中不需要手动管理这部分内存,这里的引用指的是在Lua中的使用,在C语言中调用时并不算引用。Lua的标记清除垃圾回收机制能正常检测到无引用的userdata并得以清除。</p><p>UserData需要元表的配合才完整</p><p>现在我们来扩展我们的例子,在Lua中创建了UserData之后,我们C语言中尝试获取这个UserData对应的People结构体,并修改它的name字段为&quot;MaNong&quot;:</p><p>获取UserData可以使用如下lua_touserdata方法:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/7a5ba1b8558f0.png"/> </p><p>C语言中代码调用如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>但是在实际上,调用远远没有这么简单。</p><p>上面代码第1步从Lua栈顶获取一个UserData对象,如果不是一个UserData对象,这里会返回空指针,第2步判断失败,没有问题。</p><p>但如果Lua栈顶确实是一个UserData对象,但并不是对应People结构体的UserData对象,那这里就很大问题了。这里做的操作就相当于取出一块内存,并强制当作是People结构对象,然后去操作内存里面的某个地址,尝试修改为&quot;MaNong&quot;字符串。这里强制修改内存,将可能对程序造成不可预估的错误。</p><p>所以,我们在C语言中取UserData的时候要判断这个UserData是否是我们需要的!</p><p>这个判断是通过__name元方法实现的,这个元方法可以记录一个字符串,不同的UserData类型使用不同的字符串,则通过这个名字判断我们就可以知道取出来的UserData是否是我们需要的那个UserData了。</p><p>要使用元方法,就需要用到元表。Lua源码中已经为我们提供了相应的方法,见源码《lauxlib.c》中luaL_newmetatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0af23ac708766.png"/> </p><p>这个函数的功能就是在全局环境中创建一个元表,然后设置该元表的__name为参数中的tname字符串,再配合下面这个函数使用的:</p><p>见源码《lauxlib.c》中luaL_setmetatable,作用是设置栈顶元素的metatable为叫指定__name的metatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/fd704f1f4055da2.png"/> </p><p>于是,我们此时补全一下例子中的代码:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>// 只需要提前调用一次,把__name为&quot;People&quot;的元表注册到Luastatic int RegisterPeopleMetatable(lua_State *L){  luaL_newmetatable(L, &quot;People&quot;);  return 1;} static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));    // 设置上面这个UserData的metatable为已经注册好的__name为&quot;People&quot;的元表  luaL_setmetatable(L, &quot;People&quot;);    return 1; // 新的userdata已经在栈上了}</p></section><p>此时,创建出来的People对象的UserData就会带有元表了,而元表中的__name字段为&quot;People&quot;字符串。于是我们修改我们上述改名字的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)luaL_testudata(L, 1, “People”);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>我们只是把lua_touserdata改成luaL_testudata函数,函数实现如下,区别在于调用lua_touserdata后,还会再获取它的元表,并判断元表中的__name字段,看是否我们参数中传进来的字符串,若相等才是我们真正期待获取的UserData并进行返回,否则返回NULL:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b6bb2e1a58838e1.png"/> </p><p>UserData的讲解到此结束,接下来我们看看LightUserData。</p><p>LightUserData</p><p>LightUserData是指针。比起(Full)UserData要简单很多,内存管理由交互语言,即上例中C语言管理。我们可以修改例子使用LightUserData如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  // 对象分配在C语言的堆内存中,并不在Lua中  People *pPeople = new People();    lua_pushlightuserdata(L, pPeople);   return 1; // 新的lightuserdata已经在栈上了}</p></section><p>上面函数会创建一个LightUserData对象在Lua中,对象为一个指针,指向一片内存地址,不存储实际的内存数据,上述new方法分配内存的数据存在于C语言部分的堆中。接下来再看修改后的名字设置函数:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>还是使用同样的lua_touserdata函数即可,这里会返回一个指针值,指向C语言堆中的People对象,函数内部会根据类型转化为UserData或LightUserData:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b1d6dd33681bf87.png"/> </p><p>所以我们总结来看,如果C语言结构体对象的内存希望在C语言中进行分配与管理,则可以使用LightUserData,让Lua只存储一个指针值;但若希望对象的内存在Lua中分配与管理,则需要使用UserData。</p><p>UserData与LightUserData的讲解到此完结,谢谢阅读。</p>

接下来看下图红框1部分,可知USERDATA类型只是单纯声明了一个LUA_VUSERDATA类型变体作为类型别名,没有声明多余的其它类型变体:

<p>userdata属性,数据类型之UserData</p><p>前言</p><p>本文将讲解Lua与其它编程语言(例如C语言,C++语言等)交互时常常需要使用的变量类型,分别为UserData和LightUserData,我们日常开发的时候如果只使用Lua一种编程语言,则这部分内容平时应该接触得会比较少。</p><p>交互:指不同编程语言间可以互相调用。在Lua中,与其它编程语言的调用是利用堆栈完成的,一种编程语言把内容放在栈上(一片内存区域),另一种编程语言根据定义好的数据格式解释栈上的二进制数据内容为本语言的数据结构。</p><p>定义</p><p>UserData:又有名字叫Full User Data,全量级的用户数据。通常是一块内存区域,可用于转换为特殊的数据结构,UserData内部数据的解析与设置由使用者自己进行实现,Lua并没有能力处理这份二进制内存,Lua只负责这块内存区域的内存管理与回收。</p><p>LightUserData: 轻量级用户数据,相比于上面的FullUserData,它是轻量的,它只是一个指针,在C语言中对应的类型为void*,占4或8个字节,Lua也不会管理这个指针的内存,全由使用者自己管理。</p><p>本文上半部分会讲解UserData,下半部分会讲解LightUserData。</p><p>UserData用例</p><p>我们先看UserData类型的使用方式,Lua的源码是用C语言写的,我们在本文中也将选择C语言作为与Lua交互的语言。</p><p>我们举个例子开始学习,我们在C语言中定义了如下结构体:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>struct People{  string name;  int age;}</p></section><p>为了能在Lua中使用这个结构体,我们这里就可以使用UserData。我们看下面这段C语言代码就是创建了一个UserData对象并插入到Lua堆栈顶部:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));     //设置pPeople对象的元表   // ...........(暂时省略)   return 1; // 新的userdata已经在栈上了}</p></section><p>接下来就可以在Lua中创建一个People对象并打印其名字:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li></ul><p>local people = People.New()print(people.name)</p></section><p>上面这个例子看着简单,其实我把C语言函数导出,元表设置相关的代码都省略了,本章中我们后面会再详细讲,这里先对与C语言交互简单有个印象就好了。</p><p>接下来我们就开始UserData的源码学习吧。</p><p>UserData类型定义</p><p>见源码《lua.h》,类型枚举为LUA_TUSERDATA,数值为7,二进制8位为0000&nbsp;0111:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/89827702be55771.png"/> </p><p>接下来看下图红框1部分,可知USERDATA类型只是单纯声明了一个LUA_VUSERDATA类型变体作为类型别名,没有声明多余的其它类型变体:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/47b11048950fe8c.png"/> </p><p>上图红框2,3表示UserData也可以看作为GCObject的子类,这些类在判断变量是否合法的时候都会使用ctb宏,只有满足ctb宏IS_COLLECTABLE(第7位为1)才是合法的对象。所以一个合法的UserData类型的类型标识(type tag)8位二进制为0100&nbsp;0111。</p><p>红框3中代码val_(o).gc表示UserData数据存于gc指针指向的地址上:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9c6844f55f85161.png"/> </p><p>gco2u(GCObject*)函数则把该指针转换为真正的对象:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>#define gco2u(o)  check_exp((o)-&gt;tt == LUA_VUSERDATA, &amp;((cast_u(o))-&gt;u))</p></section><p>最后返回的为下面Udata u这个数据:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/5001d7ef1e4650f.png"/> </p><p>Udata就是UserData在源码中的具体类型结构,我们下面就来详细学习一下:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9832fe2a46f9e4c.png"/> </p><p>C语言结构体占用内存存储于Udata结构体的末尾,Udata有两种使用形式:一种声明了UValue数组,可以额外存储一组数据,另一种则没有声明,而是直接后面就接上C语言结构体(结构相当于上图中的Udata0结构体),内存结构分别如下图所示,左边红色部分为使用UValue数组所多出来的部分:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0f99c877a29b.png"/> </p><p>Udata的创建代码见源码《lstring.c》luaS_newudata:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/f906693bd60650f.png"/> </p><p>Lua源码中提供了设置与获取UValue数组元素的set与get函数,不用细看:</p><p>源码《lapi.c》lua_setiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/49a1de471aeb.png"/> </p><p>源码《lapi.c》lua_getiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/81311dcce94242d.png"/> </p><p>在极大多数情况下,使用Udata都不需要用到UValue数组,因为C语言结构体也存在于Udata之中(末尾),在这个结构体中完全可以存储所需要的任何数据,不需要非得存储在UValue数组当中,所以我们可以不需要学习这一种Udata的使用方式。可以把Udata直接理解为下图的Udata0来使用:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/17fe6c03072caa4.png"/> </p><p>Udata0仅在计算Udata占用内存空间大小的时候会用于计算,实际源码中不会真正创建出Udata0类型的对象,上图中的bindata即可理解为指向C语言结构体的指针,调用luaS_newudata在C语言中创建一个userdata后,bindata这部分数据在栈顶,C语言中可直接对这部分内存强制转换为对应的C语言结构体对象,如开篇中的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));</p></section><p>知道了怎么创建一个UserData以及其内部结构。我们接下来看下删除是怎么实现的。</p><p>UserData的删除</p><p>非常简单,userdata的删除也跟其它Lua基础类型一样,当没有被引用的时候就会被清除,在C语言中不需要手动管理这部分内存,这里的引用指的是在Lua中的使用,在C语言中调用时并不算引用。Lua的标记清除垃圾回收机制能正常检测到无引用的userdata并得以清除。</p><p>UserData需要元表的配合才完整</p><p>现在我们来扩展我们的例子,在Lua中创建了UserData之后,我们C语言中尝试获取这个UserData对应的People结构体,并修改它的name字段为&quot;MaNong&quot;:</p><p>获取UserData可以使用如下lua_touserdata方法:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/7a5ba1b8558f0.png"/> </p><p>C语言中代码调用如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>但是在实际上,调用远远没有这么简单。</p><p>上面代码第1步从Lua栈顶获取一个UserData对象,如果不是一个UserData对象,这里会返回空指针,第2步判断失败,没有问题。</p><p>但如果Lua栈顶确实是一个UserData对象,但并不是对应People结构体的UserData对象,那这里就很大问题了。这里做的操作就相当于取出一块内存,并强制当作是People结构对象,然后去操作内存里面的某个地址,尝试修改为&quot;MaNong&quot;字符串。这里强制修改内存,将可能对程序造成不可预估的错误。</p><p>所以,我们在C语言中取UserData的时候要判断这个UserData是否是我们需要的!</p><p>这个判断是通过__name元方法实现的,这个元方法可以记录一个字符串,不同的UserData类型使用不同的字符串,则通过这个名字判断我们就可以知道取出来的UserData是否是我们需要的那个UserData了。</p><p>要使用元方法,就需要用到元表。Lua源码中已经为我们提供了相应的方法,见源码《lauxlib.c》中luaL_newmetatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0af23ac708766.png"/> </p><p>这个函数的功能就是在全局环境中创建一个元表,然后设置该元表的__name为参数中的tname字符串,再配合下面这个函数使用的:</p><p>见源码《lauxlib.c》中luaL_setmetatable,作用是设置栈顶元素的metatable为叫指定__name的metatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/fd704f1f4055da2.png"/> </p><p>于是,我们此时补全一下例子中的代码:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>// 只需要提前调用一次,把__name为&quot;People&quot;的元表注册到Luastatic int RegisterPeopleMetatable(lua_State *L){  luaL_newmetatable(L, &quot;People&quot;);  return 1;} static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));    // 设置上面这个UserData的metatable为已经注册好的__name为&quot;People&quot;的元表  luaL_setmetatable(L, &quot;People&quot;);    return 1; // 新的userdata已经在栈上了}</p></section><p>此时,创建出来的People对象的UserData就会带有元表了,而元表中的__name字段为&quot;People&quot;字符串。于是我们修改我们上述改名字的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)luaL_testudata(L, 1, “People”);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>我们只是把lua_touserdata改成luaL_testudata函数,函数实现如下,区别在于调用lua_touserdata后,还会再获取它的元表,并判断元表中的__name字段,看是否我们参数中传进来的字符串,若相等才是我们真正期待获取的UserData并进行返回,否则返回NULL:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b6bb2e1a58838e1.png"/> </p><p>UserData的讲解到此结束,接下来我们看看LightUserData。</p><p>LightUserData</p><p>LightUserData是指针。比起(Full)UserData要简单很多,内存管理由交互语言,即上例中C语言管理。我们可以修改例子使用LightUserData如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  // 对象分配在C语言的堆内存中,并不在Lua中  People *pPeople = new People();    lua_pushlightuserdata(L, pPeople);   return 1; // 新的lightuserdata已经在栈上了}</p></section><p>上面函数会创建一个LightUserData对象在Lua中,对象为一个指针,指向一片内存地址,不存储实际的内存数据,上述new方法分配内存的数据存在于C语言部分的堆中。接下来再看修改后的名字设置函数:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>还是使用同样的lua_touserdata函数即可,这里会返回一个指针值,指向C语言堆中的People对象,函数内部会根据类型转化为UserData或LightUserData:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b1d6dd33681bf87.png"/> </p><p>所以我们总结来看,如果C语言结构体对象的内存希望在C语言中进行分配与管理,则可以使用LightUserData,让Lua只存储一个指针值;但若希望对象的内存在Lua中分配与管理,则需要使用UserData。</p><p>UserData与LightUserData的讲解到此完结,谢谢阅读。</p>

上图红框2,3表示UserData也可以看作为GCObject的子类,这些类在判断变量是否合法的时候都会使用ctb宏,只有满足ctb宏IS_COLLECTABLE(第7位为1)才是合法的对象。所以一个合法的UserData类型的类型标识(type tag)8位二进制为0100 0111。

红框3中代码val_(o).gc表示UserData数据存于gc指针指向的地址上:

<p>userdata属性,数据类型之UserData</p><p>前言</p><p>本文将讲解Lua与其它编程语言(例如C语言,C++语言等)交互时常常需要使用的变量类型,分别为UserData和LightUserData,我们日常开发的时候如果只使用Lua一种编程语言,则这部分内容平时应该接触得会比较少。</p><p>交互:指不同编程语言间可以互相调用。在Lua中,与其它编程语言的调用是利用堆栈完成的,一种编程语言把内容放在栈上(一片内存区域),另一种编程语言根据定义好的数据格式解释栈上的二进制数据内容为本语言的数据结构。</p><p>定义</p><p>UserData:又有名字叫Full User Data,全量级的用户数据。通常是一块内存区域,可用于转换为特殊的数据结构,UserData内部数据的解析与设置由使用者自己进行实现,Lua并没有能力处理这份二进制内存,Lua只负责这块内存区域的内存管理与回收。</p><p>LightUserData: 轻量级用户数据,相比于上面的FullUserData,它是轻量的,它只是一个指针,在C语言中对应的类型为void*,占4或8个字节,Lua也不会管理这个指针的内存,全由使用者自己管理。</p><p>本文上半部分会讲解UserData,下半部分会讲解LightUserData。</p><p>UserData用例</p><p>我们先看UserData类型的使用方式,Lua的源码是用C语言写的,我们在本文中也将选择C语言作为与Lua交互的语言。</p><p>我们举个例子开始学习,我们在C语言中定义了如下结构体:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>struct People{  string name;  int age;}</p></section><p>为了能在Lua中使用这个结构体,我们这里就可以使用UserData。我们看下面这段C语言代码就是创建了一个UserData对象并插入到Lua堆栈顶部:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));     //设置pPeople对象的元表   // ...........(暂时省略)   return 1; // 新的userdata已经在栈上了}</p></section><p>接下来就可以在Lua中创建一个People对象并打印其名字:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li></ul><p>local people = People.New()print(people.name)</p></section><p>上面这个例子看着简单,其实我把C语言函数导出,元表设置相关的代码都省略了,本章中我们后面会再详细讲,这里先对与C语言交互简单有个印象就好了。</p><p>接下来我们就开始UserData的源码学习吧。</p><p>UserData类型定义</p><p>见源码《lua.h》,类型枚举为LUA_TUSERDATA,数值为7,二进制8位为0000&nbsp;0111:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/89827702be55771.png"/> </p><p>接下来看下图红框1部分,可知USERDATA类型只是单纯声明了一个LUA_VUSERDATA类型变体作为类型别名,没有声明多余的其它类型变体:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/47b11048950fe8c.png"/> </p><p>上图红框2,3表示UserData也可以看作为GCObject的子类,这些类在判断变量是否合法的时候都会使用ctb宏,只有满足ctb宏IS_COLLECTABLE(第7位为1)才是合法的对象。所以一个合法的UserData类型的类型标识(type tag)8位二进制为0100&nbsp;0111。</p><p>红框3中代码val_(o).gc表示UserData数据存于gc指针指向的地址上:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9c6844f55f85161.png"/> </p><p>gco2u(GCObject*)函数则把该指针转换为真正的对象:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>#define gco2u(o)  check_exp((o)-&gt;tt == LUA_VUSERDATA, &amp;((cast_u(o))-&gt;u))</p></section><p>最后返回的为下面Udata u这个数据:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/5001d7ef1e4650f.png"/> </p><p>Udata就是UserData在源码中的具体类型结构,我们下面就来详细学习一下:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9832fe2a46f9e4c.png"/> </p><p>C语言结构体占用内存存储于Udata结构体的末尾,Udata有两种使用形式:一种声明了UValue数组,可以额外存储一组数据,另一种则没有声明,而是直接后面就接上C语言结构体(结构相当于上图中的Udata0结构体),内存结构分别如下图所示,左边红色部分为使用UValue数组所多出来的部分:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0f99c877a29b.png"/> </p><p>Udata的创建代码见源码《lstring.c》luaS_newudata:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/f906693bd60650f.png"/> </p><p>Lua源码中提供了设置与获取UValue数组元素的set与get函数,不用细看:</p><p>源码《lapi.c》lua_setiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/49a1de471aeb.png"/> </p><p>源码《lapi.c》lua_getiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/81311dcce94242d.png"/> </p><p>在极大多数情况下,使用Udata都不需要用到UValue数组,因为C语言结构体也存在于Udata之中(末尾),在这个结构体中完全可以存储所需要的任何数据,不需要非得存储在UValue数组当中,所以我们可以不需要学习这一种Udata的使用方式。可以把Udata直接理解为下图的Udata0来使用:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/17fe6c03072caa4.png"/> </p><p>Udata0仅在计算Udata占用内存空间大小的时候会用于计算,实际源码中不会真正创建出Udata0类型的对象,上图中的bindata即可理解为指向C语言结构体的指针,调用luaS_newudata在C语言中创建一个userdata后,bindata这部分数据在栈顶,C语言中可直接对这部分内存强制转换为对应的C语言结构体对象,如开篇中的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));</p></section><p>知道了怎么创建一个UserData以及其内部结构。我们接下来看下删除是怎么实现的。</p><p>UserData的删除</p><p>非常简单,userdata的删除也跟其它Lua基础类型一样,当没有被引用的时候就会被清除,在C语言中不需要手动管理这部分内存,这里的引用指的是在Lua中的使用,在C语言中调用时并不算引用。Lua的标记清除垃圾回收机制能正常检测到无引用的userdata并得以清除。</p><p>UserData需要元表的配合才完整</p><p>现在我们来扩展我们的例子,在Lua中创建了UserData之后,我们C语言中尝试获取这个UserData对应的People结构体,并修改它的name字段为&quot;MaNong&quot;:</p><p>获取UserData可以使用如下lua_touserdata方法:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/7a5ba1b8558f0.png"/> </p><p>C语言中代码调用如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>但是在实际上,调用远远没有这么简单。</p><p>上面代码第1步从Lua栈顶获取一个UserData对象,如果不是一个UserData对象,这里会返回空指针,第2步判断失败,没有问题。</p><p>但如果Lua栈顶确实是一个UserData对象,但并不是对应People结构体的UserData对象,那这里就很大问题了。这里做的操作就相当于取出一块内存,并强制当作是People结构对象,然后去操作内存里面的某个地址,尝试修改为&quot;MaNong&quot;字符串。这里强制修改内存,将可能对程序造成不可预估的错误。</p><p>所以,我们在C语言中取UserData的时候要判断这个UserData是否是我们需要的!</p><p>这个判断是通过__name元方法实现的,这个元方法可以记录一个字符串,不同的UserData类型使用不同的字符串,则通过这个名字判断我们就可以知道取出来的UserData是否是我们需要的那个UserData了。</p><p>要使用元方法,就需要用到元表。Lua源码中已经为我们提供了相应的方法,见源码《lauxlib.c》中luaL_newmetatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0af23ac708766.png"/> </p><p>这个函数的功能就是在全局环境中创建一个元表,然后设置该元表的__name为参数中的tname字符串,再配合下面这个函数使用的:</p><p>见源码《lauxlib.c》中luaL_setmetatable,作用是设置栈顶元素的metatable为叫指定__name的metatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/fd704f1f4055da2.png"/> </p><p>于是,我们此时补全一下例子中的代码:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>// 只需要提前调用一次,把__name为&quot;People&quot;的元表注册到Luastatic int RegisterPeopleMetatable(lua_State *L){  luaL_newmetatable(L, &quot;People&quot;);  return 1;} static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));    // 设置上面这个UserData的metatable为已经注册好的__name为&quot;People&quot;的元表  luaL_setmetatable(L, &quot;People&quot;);    return 1; // 新的userdata已经在栈上了}</p></section><p>此时,创建出来的People对象的UserData就会带有元表了,而元表中的__name字段为&quot;People&quot;字符串。于是我们修改我们上述改名字的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)luaL_testudata(L, 1, “People”);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>我们只是把lua_touserdata改成luaL_testudata函数,函数实现如下,区别在于调用lua_touserdata后,还会再获取它的元表,并判断元表中的__name字段,看是否我们参数中传进来的字符串,若相等才是我们真正期待获取的UserData并进行返回,否则返回NULL:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b6bb2e1a58838e1.png"/> </p><p>UserData的讲解到此结束,接下来我们看看LightUserData。</p><p>LightUserData</p><p>LightUserData是指针。比起(Full)UserData要简单很多,内存管理由交互语言,即上例中C语言管理。我们可以修改例子使用LightUserData如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  // 对象分配在C语言的堆内存中,并不在Lua中  People *pPeople = new People();    lua_pushlightuserdata(L, pPeople);   return 1; // 新的lightuserdata已经在栈上了}</p></section><p>上面函数会创建一个LightUserData对象在Lua中,对象为一个指针,指向一片内存地址,不存储实际的内存数据,上述new方法分配内存的数据存在于C语言部分的堆中。接下来再看修改后的名字设置函数:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>还是使用同样的lua_touserdata函数即可,这里会返回一个指针值,指向C语言堆中的People对象,函数内部会根据类型转化为UserData或LightUserData:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b1d6dd33681bf87.png"/> </p><p>所以我们总结来看,如果C语言结构体对象的内存希望在C语言中进行分配与管理,则可以使用LightUserData,让Lua只存储一个指针值;但若希望对象的内存在Lua中分配与管理,则需要使用UserData。</p><p>UserData与LightUserData的讲解到此完结,谢谢阅读。</p>

gco2u(GCObject*)函数则把该指针转换为真正的对象:

#define gco2u(o)  check_exp((o)->tt == LUA_VUSERDATA, &((cast_u(o))->u))

最后返回的为下面Udata u这个数据:

<p>userdata属性,数据类型之UserData</p><p>前言</p><p>本文将讲解Lua与其它编程语言(例如C语言,C++语言等)交互时常常需要使用的变量类型,分别为UserData和LightUserData,我们日常开发的时候如果只使用Lua一种编程语言,则这部分内容平时应该接触得会比较少。</p><p>交互:指不同编程语言间可以互相调用。在Lua中,与其它编程语言的调用是利用堆栈完成的,一种编程语言把内容放在栈上(一片内存区域),另一种编程语言根据定义好的数据格式解释栈上的二进制数据内容为本语言的数据结构。</p><p>定义</p><p>UserData:又有名字叫Full User Data,全量级的用户数据。通常是一块内存区域,可用于转换为特殊的数据结构,UserData内部数据的解析与设置由使用者自己进行实现,Lua并没有能力处理这份二进制内存,Lua只负责这块内存区域的内存管理与回收。</p><p>LightUserData: 轻量级用户数据,相比于上面的FullUserData,它是轻量的,它只是一个指针,在C语言中对应的类型为void*,占4或8个字节,Lua也不会管理这个指针的内存,全由使用者自己管理。</p><p>本文上半部分会讲解UserData,下半部分会讲解LightUserData。</p><p>UserData用例</p><p>我们先看UserData类型的使用方式,Lua的源码是用C语言写的,我们在本文中也将选择C语言作为与Lua交互的语言。</p><p>我们举个例子开始学习,我们在C语言中定义了如下结构体:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>struct People{  string name;  int age;}</p></section><p>为了能在Lua中使用这个结构体,我们这里就可以使用UserData。我们看下面这段C语言代码就是创建了一个UserData对象并插入到Lua堆栈顶部:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));     //设置pPeople对象的元表   // ...........(暂时省略)   return 1; // 新的userdata已经在栈上了}</p></section><p>接下来就可以在Lua中创建一个People对象并打印其名字:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li></ul><p>local people = People.New()print(people.name)</p></section><p>上面这个例子看着简单,其实我把C语言函数导出,元表设置相关的代码都省略了,本章中我们后面会再详细讲,这里先对与C语言交互简单有个印象就好了。</p><p>接下来我们就开始UserData的源码学习吧。</p><p>UserData类型定义</p><p>见源码《lua.h》,类型枚举为LUA_TUSERDATA,数值为7,二进制8位为0000&nbsp;0111:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/89827702be55771.png"/> </p><p>接下来看下图红框1部分,可知USERDATA类型只是单纯声明了一个LUA_VUSERDATA类型变体作为类型别名,没有声明多余的其它类型变体:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/47b11048950fe8c.png"/> </p><p>上图红框2,3表示UserData也可以看作为GCObject的子类,这些类在判断变量是否合法的时候都会使用ctb宏,只有满足ctb宏IS_COLLECTABLE(第7位为1)才是合法的对象。所以一个合法的UserData类型的类型标识(type tag)8位二进制为0100&nbsp;0111。</p><p>红框3中代码val_(o).gc表示UserData数据存于gc指针指向的地址上:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9c6844f55f85161.png"/> </p><p>gco2u(GCObject*)函数则把该指针转换为真正的对象:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>#define gco2u(o)  check_exp((o)-&gt;tt == LUA_VUSERDATA, &amp;((cast_u(o))-&gt;u))</p></section><p>最后返回的为下面Udata u这个数据:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/5001d7ef1e4650f.png"/> </p><p>Udata就是UserData在源码中的具体类型结构,我们下面就来详细学习一下:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/9832fe2a46f9e4c.png"/> </p><p>C语言结构体占用内存存储于Udata结构体的末尾,Udata有两种使用形式:一种声明了UValue数组,可以额外存储一组数据,另一种则没有声明,而是直接后面就接上C语言结构体(结构相当于上图中的Udata0结构体),内存结构分别如下图所示,左边红色部分为使用UValue数组所多出来的部分:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0f99c877a29b.png"/> </p><p>Udata的创建代码见源码《lstring.c》luaS_newudata:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/f906693bd60650f.png"/> </p><p>Lua源码中提供了设置与获取UValue数组元素的set与get函数,不用细看:</p><p>源码《lapi.c》lua_setiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/49a1de471aeb.png"/> </p><p>源码《lapi.c》lua_getiuservalue:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/81311dcce94242d.png"/> </p><p>在极大多数情况下,使用Udata都不需要用到UValue数组,因为C语言结构体也存在于Udata之中(末尾),在这个结构体中完全可以存储所需要的任何数据,不需要非得存储在UValue数组当中,所以我们可以不需要学习这一种Udata的使用方式。可以把Udata直接理解为下图的Udata0来使用:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/17fe6c03072caa4.png"/> </p><p>Udata0仅在计算Udata占用内存空间大小的时候会用于计算,实际源码中不会真正创建出Udata0类型的对象,上图中的bindata即可理解为指向C语言结构体的指针,调用luaS_newudata在C语言中创建一个userdata后,bindata这部分数据在栈顶,C语言中可直接对这部分内存强制转换为对应的C语言结构体对象,如开篇中的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li></ul><p>pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));</p></section><p>知道了怎么创建一个UserData以及其内部结构。我们接下来看下删除是怎么实现的。</p><p>UserData的删除</p><p>非常简单,userdata的删除也跟其它Lua基础类型一样,当没有被引用的时候就会被清除,在C语言中不需要手动管理这部分内存,这里的引用指的是在Lua中的使用,在C语言中调用时并不算引用。Lua的标记清除垃圾回收机制能正常检测到无引用的userdata并得以清除。</p><p>UserData需要元表的配合才完整</p><p>现在我们来扩展我们的例子,在Lua中创建了UserData之后,我们C语言中尝试获取这个UserData对应的People结构体,并修改它的name字段为&quot;MaNong&quot;:</p><p>获取UserData可以使用如下lua_touserdata方法:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/7a5ba1b8558f0.png"/> </p><p>C语言中代码调用如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>但是在实际上,调用远远没有这么简单。</p><p>上面代码第1步从Lua栈顶获取一个UserData对象,如果不是一个UserData对象,这里会返回空指针,第2步判断失败,没有问题。</p><p>但如果Lua栈顶确实是一个UserData对象,但并不是对应People结构体的UserData对象,那这里就很大问题了。这里做的操作就相当于取出一块内存,并强制当作是People结构对象,然后去操作内存里面的某个地址,尝试修改为&quot;MaNong&quot;字符串。这里强制修改内存,将可能对程序造成不可预估的错误。</p><p>所以,我们在C语言中取UserData的时候要判断这个UserData是否是我们需要的!</p><p>这个判断是通过__name元方法实现的,这个元方法可以记录一个字符串,不同的UserData类型使用不同的字符串,则通过这个名字判断我们就可以知道取出来的UserData是否是我们需要的那个UserData了。</p><p>要使用元方法,就需要用到元表。Lua源码中已经为我们提供了相应的方法,见源码《lauxlib.c》中luaL_newmetatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/0af23ac708766.png"/> </p><p>这个函数的功能就是在全局环境中创建一个元表,然后设置该元表的__name为参数中的tname字符串,再配合下面这个函数使用的:</p><p>见源码《lauxlib.c》中luaL_setmetatable,作用是设置栈顶元素的metatable为叫指定__name的metatable:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/fd704f1f4055da2.png"/> </p><p>于是,我们此时补全一下例子中的代码:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>// 只需要提前调用一次,把__name为&quot;People&quot;的元表注册到Luastatic int RegisterPeopleMetatable(lua_State *L){  luaL_newmetatable(L, &quot;People&quot;);  return 1;} static int People(lua_State *L){  struct People *pPeople = (struct People *)lua_newuserdata(L, sizeof(struct People));    // 设置上面这个UserData的metatable为已经注册好的__name为&quot;People&quot;的元表  luaL_setmetatable(L, &quot;People&quot;);    return 1; // 新的userdata已经在栈上了}</p></section><p>此时,创建出来的People对象的UserData就会带有元表了,而元表中的__name字段为&quot;People&quot;字符串。于是我们修改我们上述改名字的例子:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)luaL_testudata(L, 1, “People”);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>我们只是把lua_touserdata改成luaL_testudata函数,函数实现如下,区别在于调用lua_touserdata后,还会再获取它的元表,并判断元表中的__name字段,看是否我们参数中传进来的字符串,若相等才是我们真正期待获取的UserData并进行返回,否则返回NULL:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b6bb2e1a58838e1.png"/> </p><p>UserData的讲解到此结束,接下来我们看看LightUserData。</p><p>LightUserData</p><p>LightUserData是指针。比起(Full)UserData要简单很多,内存管理由交互语言,即上例中C语言管理。我们可以修改例子使用LightUserData如下:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int People(lua_State *L){  // 对象分配在C语言的堆内存中,并不在Lua中  People *pPeople = new People();    lua_pushlightuserdata(L, pPeople);   return 1; // 新的lightuserdata已经在栈上了}</p></section><p>上面函数会创建一个LightUserData对象在Lua中,对象为一个指针,指向一片内存地址,不存储实际的内存数据,上述new方法分配内存的数据存在于C语言部分的堆中。接下来再看修改后的名字设置函数:</p><section><ul class=" list-paddingleft-2"><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li><li><p></p></li></ul><p>static int InitName(lua_State *L){  // 第 1 步  struct People *pPeople = (struct People *)lua_touserdata(L, 1);  // 第 2 步  if(pPeople != NULL)  {    pPeople-&gt;name = &quot;MaNong&quot;;  }  return 1;}</p></section><p>还是使用同样的lua_touserdata函数即可,这里会返回一个指针值,指向C语言堆中的People对象,函数内部会根据类型转化为UserData或LightUserData:</p><p style="text-align:center"><img src="http://www.hkm168.com/uploadfile/202303/b1d6dd33681bf87.png"/> </p><p>所以我们总结来看,如果C语言结构体对象的内存希望在C语言中进行分配与管理,则可以使用LightUserData,让Lua只存储一个指针值;但若希望对象的内存在Lua中分配与管理,则需要使用UserData。</p><p>UserData与LightUserData的讲解到此完结,谢谢阅读。</p>

Udata就是UserData在源码中的具体类型结构,我们下面就来详细学习一下:


本文地址:百科问答频道 https://www.neebe.cn/wenda/935433.html,易企推百科一个免费的知识分享平台,本站部分文章来网络分享,本着互联网分享的精神,如有涉及到您的权益,请联系我们删除,谢谢!


百科问答
小编:小易整编
相关文章相关阅读
  • 数据隐私是什么意思?

    数据隐私是什么意思?

    数据隐私是指保护个人隐私的概念,是建立在保护客户个人信息和保护客户权利的起点上。由于信息技术发展迅速,网络和大数据往往会被不法分子用来收集个人信息,因此,隐私保护已经成为一项重要的技术和社会问题。数据隐私一般包括以下几个方面:1.保护个人...

  • oppo手机怎么将便签数据恢复

    oppo手机怎么将便签数据恢复

    如果我们不小心将便签数据删除掉了,还能恢复吗?今天小编就告诉大家oppo手机怎么将便签数据恢复。具体如下:1.首先我们需要在手机中打开设置选项。2.当我们进入到设置界面之后我们点击上方的oppo账户。3.然后在账号窗口中点击云服务。4...

  • 数据源是什么意思?

    数据源是什么意思?

    数据源是指提供数据的源点。它收集的信息可被存储在文件、数据库或网络服务器等位置,以便进行报告分析。数据源涵盖了从有组织的定期报告到社交媒体帖子的所有信息源。一般的,数据源可以分为三大类:外部数据源、内部数据源和数据存储介质。外部数据源是指...

  • 数据处理系统是什么意思?

    数据处理系统是什么意思?

    数据处理系统(DataProcessingSystem)是一类系统,它可用来收集、整理、存储、处理和转换用户输入的数据,以便提供可读的输出。它以不同的方式建模用户的信息需求,目的主要是通过操作和处理来实现从原始数据到有用信息的转换过程...

  • 数据格式是什么意思?

    数据格式是什么意思?

    数据格式是指数据在内存中的存储方式。它使得程序能够识别和定义数据的字段、每个字段的长度以及数据存储方式,从而能够进行更有效率的处理和储存数据。换句话说,数据格式定义了数据存储在内存中的形式,从而有助于使用程序能够准确和有效地识别和理解特定...

  • 原始数据是什么意思?

    原始数据是什么意思?

    原始数据是指从某种介质中收集的原始信息,它可以是一个较舍入的统计数据、一段文字、一个照片、一个声音、一个游戏、一段视频等。它是任何一个数据处理任务的起点,并且它提供给用户真实的数据记录和可理解的信息。原始数据可以从各种数据源获取,例如互联...

  • 个人数据是什么意思?

    个人数据是什么意思?

    个人数据是某一个个体的信息,指的是每一个个体个人特有的数据。它可以是包含该个体身份、位置、社会联系、支付信息、账户信息、地理位置、日常运动情况等各种信息和数据的集合。这些信息特别的私密,具有很强的个人信息保护性,比如有的企业会对个人数据进...

  • 硬盘数据是什么意思?

    硬盘数据是什么意思?

    硬盘数据是指存储在硬盘上的数据。硬盘是电脑中最重要的一个部件,它的功能是储存和保存数据,它是一种永久存储媒介。硬盘上的数据可以代表一系列文件,如文本文件、音频文件、视频文件等,可以被定义为文件的集合。硬盘的存储空间是由磁盘板和磁头组成的,...

  • 周排行
  • 月排行
  • 年排行

精彩推荐