本篇文章是Lua设计与实现专栏的第一篇,主要结合《Lua设计与实现》书中的第二章(数据类型),以及lua5.3源码进行一些总结,由于原书中主要是基于lua5.1进行书写的,所以可能会有跟书中列举代码不一致的地方,不过大体上是保持一致的。
第一次在知乎上写技术分享,如有错误请不吝指出。
数据类型
Lua中的数据类型主要分为以下几类:
可以看到除了LUA_TNONE,lua支持了9种基本类型。其实对于某些基本类型,lua还用了一个叫做variant tag的标记来定义其子类型,具体参见下图:
比如string又细分为短string和长string,主要区别在于它们的hash值生成;而number也细分为了float和integer。
Type的组织方式
由于lua是pure C实现的,所以对于Type的组织方式,也不像C++、C#这类面向对象的语言直接可以基于继承来实现。我们先来看C里实现面向对象的两种方式:
- struct的嵌套
- 使用union包含所有可能的类型字段
lua主要采用了两种方式结合的形式。我们简单来看一下lua的类型系统牵涉的一些关键数据结构:
Value和TValue
首先,lua为了方便对所有的类型进行统一管理,把它们都抽象成了一个叫做Value的union结构,具体定义如下:
从定义可以看出,主要把这些类型划分为了需要GC的类型和不需要GC的类型。由于Value是union的结构,所以每个Value实例里同时只会有一个字段是有效的。而为了知道具体哪个字段是有效的,也就是具体该Value是什么类型,从而有了TValue这个struct结构,主要在Value基础上wrap了一个_tt字段来标识Value的具体类型。TValue的定义如下:
GCUnion、GCObject、CommonHeader
lua把所有值按是否需要被GC,划分为了GCObject和一般类型。所有需要被GC的类型,被定义在了GCUnion里:
可以发现String、UserData、Closure、Table、Proto、luaState等类型都是需要被GC的,GCUnion结构和Value类似,也是同时只有一个字段是有效的。所以我们自然而然会想到,是不是类似TValue一样,在外面给包一层type呢,但是lua实现这边并没有这样做,而是让TString、UData这些"子类"都在各自定义的开头定义这个type字段。实际上,是定义了一个叫做CommonHeader的宏,这个宏里包含了type和一些其他字段,而每个GC类型都需要在在其struct头部定义该宏,从而可以造成一种所有GC类型都继承自一个带有CommonHeader宏的基类的假象。该宏定义如下:
可以发现,它一共有三个字段:
- tt,即该GC对象的具体类型
- next,指向GCObject的指针,用于GC算法内部实现链表
- marked,用于GC算法内部实现
那这边的GCObject又是个什么东东呢?其实它就是把CommonHeader这个数据区包成了一个struct,它的好处在于lua可以把所有的GC类型的对象都视作是一个GCObject,比如在lua_State结构里就定义了一个GC列表: GCObject* gclist。 再比如,lua里创建单个gcobject的函数如下:
每个像String、Table这种子类型,它们就都会调用这个接口来创建一个GCObject实例,区别只是在于传入的type和内存size不一样而已。而这个公用函数也会帮你初始化掉CommonHeader部分的数据,每个类型只用把创建出来的实例剩余内存部分的数据设置好即可,比如下面举String类型的例子:
可以看出string在创建完成以后,调用了内部的gco2ts函数,把本来指向GCObject指针强转成了指向TString的指针,然后赋予了一些TString的额外元数据。
最后它把请求的字符串拷贝到了该TString内部,并且进一步更新了全局string的hashmap结构。String的具体实现我们会在后面String章节讲到,这里不深入研究了。