Lua是一个弱类型语言,类型可以在使用的时候变化。对应在源码中,TVable就充当了这个角色,lua中的所有数据类型都可以放在这个结构中。TValue是实现Lua数据类型的主要结构,不仅在脚本中使用了TValue,其他的一些数据结构也依赖于它。首先看看Lua的整个数据类型(lua.h)
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7 //(自定义的用户数据结构,有Light和Heavy两种,后者由Lua来分配管理,用GC)
#define LUA_TTHREAD 8 //(线程,CoRoutine)
#define LUA_NUMTAGS 9
从这里可以看出lua中有9中数据类型,其中有8种是和脚本中对应的,现在来看看TValue这个结构。
#define TValuefields Value value_; int tt_ ①
...
#undef TValuefields
...
/* little endian */
#define TValuefields \
union { struct { Value v__; int tt__; } i; double d__; } u ②
...
/* big endian */
#define TValuefields \
union { struct { int tt__; Value v__; } i; double d__; } u ③
...
struct lua_TValue {
TValuefields;
};
...
typedef struct lua_TValue TValue;
在使用的这台x86机器上,使用的是定义②。因为#undef TValuefields,定义①在后面失效。定义②中用到了 Value,在来看看Value这个结构。
typedef union Value Value;
...
union Value {
GCObject *gc; /* collectable objects 可以gc的数据类型,*/
void *p; /* light userdata 轻量用户数据*/
int b; /* booleans */
lua_CFunction f; /* light C functions */
numfield /* numbers暂时无用 */
};
Value中包含了数据结构GCObject和三种数据类型,先看看这里GCObject这个结构
union GCObject {
GCheader gch; /* common header */
union TString ts; //string类型
union Udata u; //用户数据
union Closure cl; //闭包
struct Table h; //表
struct Proto p; //函数字节码结构
struct UpVal uv; //闭包数据
struct lua_State th; /* thread 线程(协同)*/
};
其中GCheader如下
typedef struct GCheader {
CommonHeader;
} GCheader;
monHeader是一个宏,定义如下。
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
*next将所有回收的对象连成一个链表,tt表示了对象的类型,marked表示当前的回收状态。通过tt可以确定当前需要访问那个类型的值,通过marked可以确定该对象是否可以被回收。
struct lua_TValue
{
union
{
struct
{
union Value
{
union GCObject {
GCObject *next;
lu_byte tt;
lu_byte marked
union TString ts; //string类型
union Udata u; //用户数据
union Closure cl; //闭包
struct Table h; //表
struct Proto p; //函数字节码结构
struct UpVal uv; //闭包数据
struct lua_State th; /* thread 线程(协同)*/
} *gc;
void *p; /* light userdata 轻量用户数据*/
int b; /* booleans */
lua_CFunction f;/* light C functions */
numfield /* numbers暂时无用 */
} v__;
int tt__; /*数据类型*/
} i;
double d__;
} u
};
其中tt__是数据类型,如果数据类型是NUM,那么值直接存在d__中,如果是其他类型,就放在value中。Value中有GCObject,这是一个可回收的结构。CommonHeader头中包含了回收对象链,数据类型tt,和标记状态marked。GCObject 采用了union的结构,可以表示不同的值,而这些值,都有CommonHeader作为头部。这样的处理方式,使得在访问GCObject头部时,设定头部值后,里面的所有联合体访问的都是这个头部。
Lua语句
下面来看看TString这个结构,其定义如下
typedef union TString {
L_Umaxalign dummy; /* ensures maximum alignment for strings 用于最大字节对齐,这里起占位作用*/
struct {
CommonHeader; //用于gc处理的头
lu_byte extra; /* reserved words for short strings; "has hash" for longs 字符串是不是保留字符串*/
unsigned int hash; //记录字符串对应的hash值
size_t len; /* number of characters in string 字符串长度*/
} tsv;
} TString;
可以看出STring是个union,第一个成员主要是占用,用于字节对其。第二个成员是string主体,前面依然是CommonHeader,即STring也是可以gc的对象。在lua中,分为长字符串和段字符串。长度小于40的是短字符串,大于40的是长字符串(在luaconf.h中的LUAI_MAXSHORTLEN中定义)。短字符串存放在global_State->strt中,长字符串放在gc链上。下面是创建字符串用到的函数
TString *luaS_newlstr (lua_State *L, const char *str, size_t l) {
if (l <= LUAI_MAXSHORTLEN) /* short string? */
return internshrstr(L, str, l);
else {
if (l + 1 > (MAX_SIZET - sizeof(TString))/sizeof(char))
luaM_toobig(L);
return createstrobj(L, str, l, LUA_TLNGSTR, G(L)->seed, NULL);
}
}
对于短字符串,在创建的时候,首先计算str的hash值。计算时会获得一个随机种子,这个种子就是global_State->seed。然后通过LUAI_HASHLIMIT控制步长,每一个步长范围内取字符串中的一个字符,和上次hash的结果相加,得到新的hash结果。计算出hash后就开始找是否存在这个字符串,方法是遍历global_State->strt->hash,global_State->strt结构定义如下。短字符串表申请内存的大小和实际使用大小由后两个字段表示。
typedef struct stringtable {
GCObject **hash; //保存所有的字符串
lu_int32 nuse; /* number of elements 已装元素的个数*/
int size; //当前hash桶的大小
} stringtable;
GCObject是一个数组,数组中每个元素是一个STring同义词链,每新建的一个元素其hash值通过lmod方法得在数组中的位置,然后挂在在位置的list链上。这种方类似于用链地址表法解决同义词冲突,方便查找和删除。
遍历global_State->strt->hash数组时,首先是比较hash,相同再比较len(字符串长度),相同在判断原始str是否相同。通过两个&&关系起到了过滤的作用,可以加快判断的速度。如果找到有相同的字符串,那就直接返回找到的STting,没有找到,就需要创建新的段字符串,通过函数newshrstr实现。先创建一个LUA_TSHRSTR结构,然后存放到global_State->strt->hash中。
对于长字符串,创建的是LUA_TLNGSTR类型结构。会直接createstrobj创建一个gc对象,挂到gc链上。长字符串一般是使用的文本,在新建的时候不计算hash值,也不保证唯一性,使用extra字段来表示是否计算hash。