Lua 作为一门嵌入式的脚本语言,可以绕过编译这一漫长的步骤、可以很轻松地热加载,并且可以嵌入各种语言系统,因此很多游戏项目中会使用 Lua。我们项目中前后端共用的代码和一些改动频率比较大、对效率没那么敏感的代码都会使用 Lua 实现。

而 Lua 有个重要特性是弱类型,而且 Lua 是 C 语言实现的,对此有点好奇,因此这次想来研究下。

# 1.Lua 数据的结构

Lua 是弱类型的脚本语言,因此 Lua 的所有数据类型需要有个统一的、通用的数据结构去保存,并且需要在使用时区分数据类型。Lua 实现使用了一个变量记录数据的类型,然后使用 union 保存具体的数据。

Lua 最终形成的是一个 TValue 的结构体:

typedef struct lua_TValue {
  TValuefields;
} TValue;

从下图可以看到 TValue 的结构:

Lua数据结构

可以一层一层看下每个成员的作用。

# 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_TTABLETable
LUA_TFUNCTION函数CClosure、LClosure
LUA_TUSERDATA指针void *
LUA_TTHREADLua 虚拟机、协程lua_State

Lua 中判断类型是否需要 GC 是通过 iscollectable 接口判断是否需要 GC。

p
// 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 之间的爱恨纠葛

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Nirvana 支付宝

支付宝