Lua的设计与实现——基础数据类型(Lua中的数据类型)

Lua的设计与实现——基础数据类型(Lua中的数据类型)

出自书籍:李创.Lua设计与实现[M].北京.人民邮电出版社.2017:186.

  在这一部分中,探讨Lua中的基础数据结构。Lua内部采用一种通用的基础数据结构来表示数据类型。Lua语言及其精简,只有字符串和表两种最基本的数据结构。然而,精简并不代表简陋,在这些基础数据结构的实现中,处处可以看到设计者为了性能和可扩展性(这是Lua从一开始就坚持的目标)所做的努力。

  Lua是一门动态类型的脚本语言,这意味着同一个变量可以在不同时刻指向不同类型的数据。

  在Lua中,我们使用一个通用的数据结构lua_TValue来统一表示所有在Lua虚拟机中需要保存的数据类型,这里将这个通用数据结构一层一层地拆解开来介绍。但是开始讨论Lua的实现之前,为了便于对比,我们先来看看在C语言实现相似的功能,一般做法是怎样的。

C语言实现通用数据结构的一般做法

  在开始阅读具体代码之前,首先需要想想,如果要使用一个通用的数据结构来表示不同的数据类型,一般的做法应该是这样的。

  • 需要一个字段来存储数据的类型。
  • 需要存储不同的数据类型的数据。

这里又有两种比较常见的做法。

  • 定义一个公共的数据结构作为基础类型,里面存储的都是表达这个数据的基础信息,其它具体的类型是从这里派生出来的。这就是一般的面向对象的思路。

    鉴于Lua使用的是C语言,可以使用类似下面的代码来模拟实现面向对象:

    struct base //定义基础的数据信息
    {
        int type;
    };
    
    struct string
    {
        struct base info;
        int len;
        char *data[0];
    };
    
  • 使用联合(union)来讲所有数据包进来,类似下面的代码:

    struct string
    {
        int len;
        char* data[0];
    };
    
    struct number
    {
        double num;
    };
    
    struct value
    {
        int type;
        union
        {
            string str;
            number num;
        }value;
    };
    

  两种做法各有利弊。在Lua代码中,一般采用两种做法相结合的方式。

Lua通用数据结构的实现

  在Lua一开始的设计中,主要有以下几种类型:数字(使用double类型表示)、字符串、关联表、nil、userdata、Lua函数以及C函数。一开始,我们并没有加入布尔类型的数据,同时Lua函数和C函数是分开表示的。

  在演进到5.1.4版本时,加入了THREAD类型以及布尔类型(详见表2-1),同时也将两种函数合并在了一起:

#define LUA_TNONE		(-1) --无类型

#define LUA_TNIL		0 --nil
#define LUA_TBOOLEAN		1 --布尔类型
#define LUA_TLIGHTUSERDATA	2 --指针(void*)
#define LUA_TNUMBER		3 --数字类型
#define LUA_TSTRING		4 --字符串类型
#define LUA_TTABLE		5 --表类型
#define LUA_TFUNCTION		6 --函数类型
#define LUA_TUSERDATA		7 --指针(void*)
#define LUA_TTHREAD		8 --Lua虚拟机、协程

#define LUA_NUMTAGS		9 --共9中类型(不包含LUA_TNONE)
表2-1 Lua中的数据类型
类型对应数据结构
LUA_TNONE无类型
LUA_TNIL空类型
LUA_TBOOLEAN布尔类型
LUA_TLIGHTUSERDATA指针void *
LUA_TNUMBER数据lua_Number
LUA_TSTRING字符串TString
LUA_TTABLETable
LUA_TFUNCTION函数CClosure、LClosure
LUA_TUSERDATA指针void *
LUA_TTHREADLua虚拟机、协程lua_State

  其中LUA_TLIGHTUSERDATA和LUA_TUSERDATA一样,对应的都是void *指针,区别在于前者分配释放由Lua外部的使用者来完成,而后者则是由Lua内部来完成的。换言之,前者不需要Lua去关心它的生存期,由使用者自己去关注,后者则反之。

  Lua内部用一个宏表示哪些数据类型需要进行GC(Garbage Collection,垃圾回收)操作:

//(lobject. h) 
#define iscollectable(o) (ttype(o) >= LUA_TSTRING)

  可以看到,LUA_TSRING(包括LUA_TSTRING)之后的数据类型都需要进行GC操作。

  那么,这些需要进行GC操作的数据类型,在Lua中是如何表示的呢?

  这些需要进行GC操作的数据类型都会有一个CommonHeader宏定义的成员,并且这个成员在结构体定义的最开始部分。比如,用于表示表结构类型的Table是这么定义的:

//(lobject. h) 
typedef struct Table { 
	CommonHeader; 
	lu _byte flags; /* 1< p means tag 「「1ethod ( p) is not present */
	lu_byte lsizenode; /* log2 of size of node ’ array */ 
	struct Table netatable;
	TValue *array; /* array part */
	Node *node; 
	Node *lastfree; /* any free position is before this position */ 
	GCObject *gclist; 
	int sizearray; /* size array array */ 
} Table;

  其中CommonHeader的定义如下:

//(lobject. h) 
/*
** Common Header for all collectable objects (in macro form, to be
** included in other objects)
*/
#define CommonHeader	GCObject *next; lu_byte tt; lu_byte marked

里面的几个成员定义如下。

  • next:指向下一个GC链表的成员。
  • tt:表示数据的类型,及前面的那些表示数据类型的宏。
  • marked:GC相关的标记位。

同时,还有一个名为GCheader的结构体,其中的成员只有CommonHeader;

//(lobject. h) 
/* 
** Common header in struct form 
*/ 
typedef struct GCheader { 
	CommonHeader; 
} GCheader; 

于是,在Lua中就使用了GCObject联合体将所有需要进行垃圾回收的数据类型囊括了进来:

//(lstate.h) 
/* 
Union of all collectable objects 
*/ 
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 */ 
}; 

  整理一下前面提到的这几个结构体,可以得到这样的结论。

  • 任何需要进行垃圾回收处理的Lua数据类型,必然以CommonHeader作为该结构体定义的最开始部分。如果熟悉C++类的实现原理,可以将CommonHeader这个成员理解为一个基类的所有成员,而其他需要回收处理的数据类型均从这个基类继承下来,所以它们的结构体定义的开始部分都是这个成员。

  • GCObject这个联合体,将所有需要进行垃圾回收的数据类型囊括其中,这样定位和查找不同类型的数据时就方便多了。而如果只想要它们的GC部分,可以通过GCheader gch,如:

    //(lobject.h) 
    #define gcvalue(o) check_exp(iscollectable(o), (o)->value.gc)
    

    仅表示需要进行垃圾回收的数据类型还不够,还有几种数据类型是不需要进行垃圾回收的,Lua中将GCObject和它们一起放在了联合体Value中:

    //(lobject.h) 
    /* 
    ** Union of all Lua values 
    */ 
    typedef union { 
    	GCObject *gc; 
    	void *p; 
    	lua_Number n; 
    	int b; 
    } Value;
    

    到了这一步,差不多可以表示Lua中所有的数据类型了。但是还欠缺了一点东西,那就是这些数据到底是什么类型的。于是Lua代码中又有了TValuefields,它用于将Value和类型结合在一起:

    //(lobject.h) 
    #define TValuefields Value value; int tt
    

    这最后形成了Lua中的TValue结构体,Lua中的任何数据都可以通过该结构体表示:

    //(lobject.h) 
    typedef struct lua TValue { 
    	TValuefields; 
    } TValue; 
    

    Lua通用数据结构的组织如图2-1所示。

效果图

图2-1 Lua通用数据结构的组织

  前面提到过,Lua同时采用了两种方式来做到数据统一。根据前面的分析,这表现在以下两个方面。

  • 具体类型中有CommonHeader,用于存放所有数据类型都通用的字段。
  • TValue作为统一表示所有数据的数据结构,内部使用了联合体Value将所有数据都包起来。

  在具体的代码视线中,TValue用于统一表示数据,而一旦知道了具体的类型,就需要使用具体的类型了。因此,代码中有不少涉及TValue与具体类型之间转换的代码,其主要逻辑都是将TValue中的tt、value与具体类型的数据进行转换。比如,将lua_Number转换为TValue的宏setnvalue的代码是这样的:

//(lobject. h) 
#define setnvalue(obj,x) \ 
{ TValue *i_o = (obj); i_o->value.n=(x); i_o- >tt=LUA_TNUMBER; } 

  这部分的代码逻辑和命名都差不多,这里就不再列出了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
993 年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro in Brazil)诞生了一门编程语言,发明者是该校的三位研究人员,他们给这门语言取了个浪漫的名字——Lua,在葡萄牙语里代表美丽的月亮。事实证明她没有糟蹋这个优美的单词,Lua语言正如它名字所预示的那样成长为一门简洁、优雅且富有乐趣的语言Lua从一开始就是作为一门方便嵌入(其它应用程序)并可扩展的轻量级脚本语言设计的,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用ANSI C编写,能以C程序库的形式嵌入到宿主程序Lua的每个版本都保持着开放源码的传统,不过各版采用的许可协议并不相同,自5.0版(最新版是5.1) 开始她采用的是著名的MIT许可协议。正由于上述特点,所以Lua在游戏开发、机器人控制、分布式应用、图像处理、生物信息学等各种各样的领域得到了越来越广泛的应用。其尤以游戏开发为最,许多著名的游戏,比如Escape from Monkey Island、World of Warcraft、大话西游,都采用了Lua来配合引擎完成数据描述、配置管理和逻辑控制等任务。 作为一门过程型动态语言Lua有着如下的特性:1、变量名没有类型,值才有类型,变量名在运行时可与任何类型的值绑定;2、语言只提供唯一一种数据结构,称为表(table),它类似key-value关联数组,可以用任何类型的值作为key和value。提供了一致且富有表达力的表构造语法,使得Lua很适合描述复杂的数据;3、函数是一等类型,支持匿名函数和正则尾递归(proper tail recursion);4、支持词法定界(lexical scoping)和闭包(closure);5、提供thread类型和结构化的协程(coroutine)机制,在此基础上可方便实现协作式多任务;6、运行期能编译字符串形式的程序文本并载入虚拟机执行;7、通过元表(metatable)和元方法(metamethod)提供动态元机制(dynamic meta-mechanism),从而允许程序运行时根据需要改变或扩充语法设施的内定语义;8、能方便地利用表和动态元机制实现基于原型(prototype-based)的面向对象模型;9、从5.1版开始提供了完善的模块机制,从而更好地支持开发大型的应用程序; Lua 的语法类似PASCAL和Modula但更加简洁,所有的语法产生式规则(EBNF)不过才60几个。熟悉C和ASCAL的程序员一般只需半个小时便可将其完全掌握。而在语义上Lua则与Scheme极为相似,她们完全共享上述的1、3、4、6点特性,Scheme的continuation与协程也基本相同只是自由度更高。最引人注目的是,两种语言都只提供唯一一种数据结构:Lua的表和Scheme的列表(list)。正因为如此,有人甚至称Lua为“只用表的Scheme”。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ufgnix0802

总结不易,谢谢大家的支持

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值