• 设为首页
  • 点击收藏
  • 手机版
    手机扫一扫访问
    迪恩网络手机版
  • 关注官方公众号
    微信扫一扫关注
    公众号

lua数据类型

原作者: [db:作者] 来自: [db:来源] 收藏 邀请
1.TValue
     typedef struct
     {
          Value value;
          int tt;
     } TValue
     lua所有类型,Value是union,
     typedef union
     {
          // GCObject *gc; 可以gc的类型
           GCheader gch;  // 头部
           union TString ts; // 字符串
           union Udata u;  // userdata
           union Closure cl; // 闭包函数
           struct Table h;  // 表
           struct Proto p;   // 函数原型
           struct UpVal uv;  // upvalue
           struct lua_State th;  /* thread */

          // 不可gc的类型
          void *p;
          lua_Number n;
          int b; 
      } Value

         GCHeader头部信息:
          typedef struct
          {
               GCObject *next; lu_byte tt; lu_byte marked
          }GCHeader;


2.字符串类型
     typedef union TString {
        L_Umaxalign dummy;  /* ensures maximum alignment for strings */
        struct {
               CommonHeader;
               lu_byte reserved;
               unsigned int hash;
               size_t len;
                  } tsv;
           } TString;

     这个CommonHeader就是GCheader,其实是同一块内存,这样写法,在GCObject和具体类型中都可以访问到头部。
     lua里字符串内存布局:字符串类型表现形式是TString指针,其实是一个TString头部包含字符串描述信息,紧接着才是一个(TString->len)长度的字符串内容,并加一个'\0'字符。
     lua里保存字符串的方式:global state有一个全局字符串表strt(stringtable类型),其实是一个哈希表,通过一种哈希算法(保存在TSreing->hash)计算出哈希值,保存到对应表项,对于冲突项使用链表解决(TString->tsv->next保存下一个相同哈希值的字符串)。
     lua的这种机制,使得相同的字符串公用一块内存,节省了不少内存,同其他GC对象一样,字符串都是引用着存在。看下新创建一个字符串类型的过程:

TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
76   GCObject *o;
77   unsigned int h = cast(unsigned int, l);  /* seed */
78   size_t step = (l>>5)+1;  /* if string is too long, don't hash all its chars */
79   size_t l1;
80   for (l1=l; l1>=step; l1-=step)  /* compute hash */
81     h = h ^ ((h<<5)+(h>>2)+cast(unsigned char, str[l1-1]));
82   for (o = G(L)->strt.hash[lmod(h, G(L)->strt.size)];
83        o != NULL;
84        o = o->gch.next) {
85     TString *ts = rawgco2ts(o);
86     if (ts->tsv.len == l && (memcmp(str, getstr(ts), l) == 0)) {
87       /* string may be dead */
88       if (isdead(G(L), o)) changewhite(o);
89       return ts;      
90     }         
91   }            
92   return newlstr(L, str, l, h);  /* not found */
93 }
94 

3.table类型
     typedef struct Table {
  CommonHeader;
  lu_byte flags;  /* 1<<p means tagmethod(p) is not present */
  lu_byte lsizenode;  /* log2 of size of `node' array */
  struct Table *metatable;
  TValue *array;  /* array part */
  Node *node;
  Node *lastfree;  /* any free position is before this position */
  GCObject *gclist;
  int sizearray;  /* size of `array' array */
} Table;
     Table类型被分为两部分了,一部分是array一部分是hash table,这样可以对用户使用数组进行优化,lua_creatable(L, narray, nrec) API :narray代表数组部分大小,nrec代表hash表部分大小。
     对于数组部分的实现没啥好说了,看下哈希表的算法。
     哈希表数据结构:

typedef union TKey {
  struct {
    TValuefields;
    struct Node *next;  /* for chaining */
  } nk;
  TValue tvk;
} TKey;


typedef struct Node {
  TValue i_val;
  TKey i_key;
} Node;
     其实是一个Node数组,Node分为key,value字段,key其实也可以是任何value,TKey又使用了之前的技巧,nk,tvk是union的字段,公用同样的内存,这样既可TKey->tvk取值,又可TKey->nk->..取值,另外key保存了一个指向冲突表项的指针。
     总而言之,这个哈希表是这样的:内存上只是一块Node的连续数组,插入时:依据key算出hash value,mod到表项,如果这一表项是空项,直接插入。如果冲突,先取一块新的空表项(根据Table的lastfree取),之后判断冲突表项的key hash后是否应该放在自己所在的位置,如果不应该放在这,把它挪到刚取出的空表项,腾出的表项放新key;如果确实是放在这,也就是与新key有同样的hash value,则把新key插入空表项,并设计好链接关系。看下代码吧:
     static TValue *newkey (lua_State *L, Table *t, const TValue *key) {
  Node *mp = mainposition(t, key);
  if (!ttisnil(gval(mp)) || mp == dummynode) {
    Node *othern;
    Node *n = getfreepos(t);  /* get a free place */
    if (n == NULL) {  /* cannot find a free place? */
      rehash(L, t, key);  /* grow table */
      return luaH_set(L, t, key);  /* re-insert key into grown table */
    }
    lua_assert(n != dummynode);
    othern = mainposition(t, key2tval(mp));
    if (othern != mp) {  /* is colliding node out of its main position? */
      /* yes; move colliding node into free position */
      while (gnext(othern) != mp) othern = gnext(othern);  /* find previous */
      gnext(othern) = n;  /* redo the chain with `n' in place of `mp' */
      *n = *mp;  /* copy colliding node into free pos. (mp->next also goes) */
      gnext(mp) = NULL;  /* now `mp' is free */
      setnilvalue(gval(mp));
    }
    else {  /* colliding node is in its own main position */
      /* new node will go into free position */
      gnext(n) = gnext(mp);  /* chain new position */
      gnext(mp) = n;
      mp = n;
    }
  }
  gkey(mp)->value = key->value; gkey(mp)->tt = key->tt;
  luaC_barriert(L, t, key);
  lua_assert(ttisnil(gval(mp)));
  return gval(mp);
}
     
     看下接口设计,每次插入时,luaH_set其实是返回了这个key对应的value指针,所以使用时类似这样  setobj2t(L, luaH_set(L, hvalue(t), L->top-2), L->top-1);

4.函数类型
 

typedef struct CClosure {
  ClosureHeader;
  lua_CFunction f;
  TValue upvalue[1];
} CClosure;


typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;
  UpVal *upvals[1];
} LClosure;


typedef union Closure {
  CClosure c;
  LClosure l;
} Closure;

#define ClosureHeader \
     CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \
     struct Table *env


  分为c函数闭包和lua函数闭包,其实都是包含函数原型,upvalues,环境表这三个部分。关于c函数闭包实现比较简单,主要是通过接口lua_pushcclosure创建,函数实现中可以通过lua_getvalue访问upvalue。lua函数闭包和虚拟机相关,后面再描述。

5.upvalue
upvalue不是单独的一个对外可用的类型,他是Closure的一部分,是为了引用在这个函数外部的局部变量而设计的。许多语言限制了这种特性,比如python用作用域限制访问外部的局部变量,pascal的函数类型不是第一类类型,即你必须保证外部的变量一直在栈中,以求引用时,内存仍然存在。c语言两种限制方式都存在。lua里面使用upvalue,以很少的代码实现了访问函数外部局部变量的特性。看定义:


/*
** Upvalues
*/

typedef struct UpVal {
  CommonHeader;
  TValue *v;  /* points to stack or to its own value */
  union {
    TValue value;  /* the value (when closed) */
    struct {  /* double linked list (when open) */
      struct UpVal *prev;
      struct UpVal *next;
    } l;
  } u;
} UpVal;

UpVal有两个状态open和closed:
     open状态是指UpVal所引用的值还在外部函数栈上保存,这时候UpVal->v指向栈内存,UpVal->prev和UpVal->next则是用于把所有UpVal链接到一个链表里,链表保存在lua_State的openupval,这样如果有不同函数引用同一个变量,就可以直接使用这个UpVal,而不需要再创建一个新的。
     closed状态是指UpVal所引用的值所在的栈已经被释放了,例如:
     function add (x)
          return function (y) return y + x end
     end
     add2 = add(2)
     print(add2())
     当执行add2()时add2引用的x所在的栈已经释放了,这时,lua会复制x到UpVal->l.value,并且UpVal->v指向自己的value。


     

鲜花

握手

雷人

路过

鸡蛋
该文章已有0人参与评论

请发表评论

全部评论

专题导读
上一篇:
Lua协程发布时间:2022-07-22
下一篇:
lua 5.0的实现(翻译)4,5发布时间:2022-07-22
热门推荐
热门话题
阅读排行榜

扫描微信二维码

查看手机版网站

随时了解更新最新资讯

139-2527-9053

在线客服(服务时间 9:00~18:00)

在线QQ客服
地址:深圳市南山区西丽大学城创智工业园
电邮:jeky_zhao#qq.com
移动电话:139-2527-9053

Powered by 互联科技 X3.4© 2001-2213 极客世界.|Sitemap