Lua中Userdata类型源码实现

   1、概述
    Lua中userdata分两种,一种是轻量级userdata(light userdata),轻量级userdata是一种表示C指针的值,对Lua虚拟机来说,这种数据类型不需要GC(垃圾回收),其指向的内存由用户分配和释放,其实现就是一个void *p指针;后一种userdata类型完全userdata(full userdata),内存是由Lua虚拟机分配,并有GC机制负责处理。下面将通过Lua 5.2.1的源码来看后一种userdata的实现。
     2、源码实现

    userdata内存存储形式类似于字符串,以一个头开始然后紧随头后面保存相关的数据,但是每个userdata都拥有自己独立的元表。下面是userdata对应的头数据结构Udata代码(lobject.h):

[cpp]  view plain  copy
  1. 431 /*                                                                                                  
  2. 432 ** Header for userdata; memory area follows the end of this structure                               
  3. 433 */                                                                                                   
  4. 434 typedef union Udata {                                                                                
  5. 435   L_Umaxalign dummy;  /* ensures maximum alignment for `local' udata */                              
  6. 436   struct {                                                                                           
  7. 437     CommonHeader;                                                                                    
  8. 438     struct Table *metatable;                                                                         
  9. 439     struct Table *env;                                                                               
  10. 440     size_t len;  /* number of bytes */                                                               
  11. 441   } uv;                                                                                              
  12. 442 } Udata;  

成员dummy和CommonHeader含义与TString完全一样。其他成员含义如下:
    struct Table *metatable:指向userdata对应的元表,也就是一个table。可以调用luaL_newmetatable创建一个元表(注意是在注册表上创建的,也就是说userdata的元表保存在注册表中),然后调用lua_setmetatable来设置userdata的元表,注意Lua代码中不能改变userdata的元表。
    struct Table *env:userdata的环境,创建userdata时该值为NULL。可以通过调用lua_setuservalue函数来设置userdata的环境。
    size_t len:保存userdata内存的大小,就紧随头后面数据内存的大小。
userdata的数据部分和字符串一样,都是紧接着放在结构后面,创建userdata的代码如下(lstring.c):

[cpp]  view plain  copy
  1. 175 Udata *luaS_newudata (lua_State *L, size_t s, Table *e) {                                            
  2. 176   Udata *u;  
  3. 177   if (s > MAX_SIZET - sizeof(Udata))  
  4. 178     luaM_toobig(L);  
  5. 179   u = &luaC_newobj(L, LUA_TUSERDATA, sizeof(Udata) + s, NULL, 0)->u;                                 
  6. 180   u->uv.len = s;  
  7. 181   u->uv.metatable = NULL;  
  8. 182   u->uv.env = e;  
  9. 183   return u;                                                                                          
  10. 184 }    

 lua给 c/c++暴露的接口  

LUA_API void *lua_newuserdata (lua_State *L, size_t size) {
  Udata *u;
  lua_lock(L);
  u = luaS_newudata(L, size);  //在lua中创建出一个userdata数据结构 
  setuvalue(L, L->top, u);     //然后将其放置在栈顶
  api_incr_top(L);	       //增加栈索引	
  luaC_checkGC(L);	      	
  lua_unlock(L);
  return getudatamem(u);	//返回存储数据的内存地址
}
可以看到这里主要是调用了luaS_newudata  最后返回分配的内存地址

#define getudatamem(u)  \
  check_exp(sizeof((u)->len), (cast(char*, (u)) + sizeof(UUdata)))
check_exp 检测分配的字节数是否为0   

3、总结
    I、

    I、 lua中的userdata类型主要用来表示在C/C++中定义的类型,即用来实现扩展lua,这些扩展代码通常是用C/C++来实现的。对lua 虚拟机来说userdata只是提供了一块原始的内存区域,可以用来存储任何东西,并且在lua中userdata没有任何预定义的操作。注意这块分配的额外内存是由Lua垃圾收集器来管理的,无须关心起释放等情况。
     II、 userdata的元表通常用来存储对应C/C++类型的方法,这样在脚本中可以直接调用userdata对应的方法,并且这些方法也是用C/C++实现的。
     III、 创建一个新的userdata时,其环境表默认值是空,即成员值env 为NULL,当前这个成员在Lua没被使用。在一些文章指出,这个成员可以保存与userdata实例相关的数据,而其对应的元表保存userdata的方法。

猜你喜欢

转载自blog.csdn.net/dingqinghui/article/details/78002005