在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
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; // userdataunion 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。
|
请发表评论