Lua 作为一门嵌入式的脚本语言,可以绕过编译这一漫长的步骤、可以很轻松地热加载,并且可以嵌入各种语言系统,因此很多游戏项目中会使用 Lua。我们项目中前后端共用的代码和一些改动频率比较大、对效率没那么敏感的代码都会使用 Lua 实现。
而 Lua 有个重要特性是弱类型,而且 Lua 是 C 语言实现的,对此有点好奇,因此这次想来研究下。
# 1.Lua 数据的结构
Lua 是弱类型的脚本语言,因此 Lua 的所有数据类型需要有个统一的、通用的数据结构去保存,并且需要在使用时区分数据类型。Lua 实现使用了一个变量记录数据的类型,然后使用 union 保存具体的数据。
Lua 最终形成的是一个 TValue 的结构体:
typedef struct lua_TValue { | |
TValuefields; | |
} TValue; |
从下图可以看到 TValue 的结构:
可以一层一层看下每个成员的作用。
# i.TValuefields
#define TValuefields Value value; int tt |
最外层是一个 TValueFields 的宏。包含了一个 Value 和 tt。tt 是 int 值,记录了数据的数据类型。
# ii.Value
typedef union { | |
GCObject *gc; | |
void *p; | |
lua_Number n; | |
int b; | |
} Value; |
Value 是负责记录数据的结构,但是 Lua 中的类型分需要 GC(Garbage Collection)和不需要 GC 的,而需要 GC 的类型需要一些数据来支持 GC 机制,因此这类数据就被包装在 GCObject 这个类型中,而其余不需要 GC 的则直接放作为联合体的其他成员包含在 Value 对象里。
以下是 Lua 中所有的数据类型:
- Lua 中的数据类型
宏 | 类型 | 数据结构 |
---|---|---|
LUA_TNONE | 无类型 | 无 |
LUA_TNIL | 空类型 | 无 |
LUA_TBOOLEAN | 布尔类型 | int |
LUA_TLIGHTUSERDATA | 指针 | void * |
LUA_TNUMBER | 数据 | lua_Number |
LUA_TSTRING | 字符串 | TString |
LUA_TTABLE | 表 | Table |
LUA_TFUNCTION | 函数 | CClosure、LClosure |
LUA_TUSERDATA | 指针 | void * |
LUA_TTHREAD | Lua 虚拟机、协程 | lua_State |
Lua 中判断类型是否需要 GC 是通过 iscollectable 接口判断是否需要 GC。
// 5.1: | |
#define ttype(o) ((o)->tt_) | |
#define iscollectable(o) (ttype(o) >= LUA_TSTRING) | |
// 5.4: | |
#define BIT_ISCOLLECTABLE (1 << 6) | |
#define rawtt(o) ((o)->tt_) | |
#define iscollectable(o) (rawtt(o) & BIT_ISCOLLECTABLE) |
可以看到 5.1 中,LUA_TSTRING 开始的都是需要 GC 的,所以这些数据都包含在 GCObject 中,而上面的除空的数据分别对应了结构中的 b,n 和 p 成员。而 5.4 中已经变为使用 tt 的第六位来判断,可能是为了处理一些特殊情况。
# iii.GCObject
union GCObject { | |
GCheader gch; | |
union TString ts; | |
union Udata u; | |
union Closure cl; | |
struct Table h; | |
struct Proto p; | |
struct UpVal uv; | |
struct lua_State th; /* thread */ | |
}; |
GCObject 是个联合体,除 GCheader 外每个成员对应了所有需要 GC 的数据类型,每个类型的结构体定义开头都是一个 CommonHeader,里面包含了 GC 相关的数据。GCheader 的结构如下,只有一个 CommonHeader,所以当你只需要 GC 相关数据时就可以使用 gch 这个成员。
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked | |
typedef struct GCheader { | |
CommonHeader; | |
} |
可以看到 GCheader 中有三个成员:
next:Lua 的 GC 机制中会有一个链表管理需要 GC 的对象,指向 GC 链表的下一个成员。
tt:表示数据的类型。
marked:GC 相关的标记位。
##2. 总结
Lua 语法十分简洁,且是速度最快的脚本语言之一,实现用的还是相当难掌控的 C,其设计还是有很多精巧之处的,接下来还会接着研究。最后还有个新了解到的,关于为什么很多 C 和 C++ 的库中定义 struct 的时候会在外面包一层 typedef。这是因为 C 中单单定义 struct 的话,之后声明该类会需要这样用:
struct A{ | |
... | |
}; | |
struct A a; |
这样就代码就会比较啰嗦,C++ 中优化了这一点,不过有些 C++ 库的作者可能保留了习惯或是为了兼容 C 所以也有这样的写法。
# 参考文章
《Lua 设计与实现》——codedump
lua 5.3.5 TValue::tt_和 GCObject::tt 之间的爱恨纠葛