Lua 5.1.1 源代码阅读笔记:

本文详细解析了 Lua 5.1.1 的源代码,介绍了 Lua 的数据类型、数据结构及内存管理机制。重点讨论了 Lua 的数据表示方式,包括 TString 和 Udata 结构,以及 Lua 虚拟机的状态结构 lua_State。
 
Lua 5.1.1 源代码阅读笔记:
(推荐在Notepad++中打开并开启C语言模式)
   
去年的《程序员》上半年部分除了第二期之外,其他的都不怎么样;但是今年的却不一样,特别是算法与开源手册这两章,真实极务实的。这一期中,发现 了Lua 这块圭玉,十分欣喜。下载把玩两天后,感觉语法不怎么样,有点混,不清晰,除此之外,定位什么的都相当精彩!昨天读了读 "The implementation of Lua 5.0",感触挺多的,今天决定看看源代码,试炼一下。
一段一段的说吧,先是数据表示的数据结构:(Lua.h 72~82)
#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
#define LUA_TTHREAD  8
以上是Lua的八种结构(虽然上面不止八种):
① Nil(nil空类型,类似于Scheme的‘(),Python的None),
② Boolean(布尔值),
③ Number(数字是C中的Double,和PASCAL中的Real,只在一些特殊的机型上才会被替换为Integer),④String(字符串不是C的ASCIZ,是类PASCAL型),
⑤ Table(就是一个Table,但是可以用任意的类型(除nil)来索引任意的类型,5.0后使用了侯捷先生所谓的无痛哲学,包装了Table和Array)
⑥ Function(当然是函数了,即所谓的First-Class,广泛用于Scheme,ML中,相当的方便)
⑦ UserData(自定义的用户数据结构,有Light和Heavy两种,后者由Lua来分配管理,用GC)
⑧ Thread(线程,CoRoutine)
怎么样,上面几个#define就明白了吧?(Lua的作者们似乎受PASCAL的影响很深,从T前缀可见;另外从命名法可以看出)
接着:(lua.h 120~126)
LUA_API int   (lua_gettop) (lua_State *L);
LUA_API void  (lua_settop) (lua_State *L, int idx);
LUA_API void  (lua_pushvalue) (lua_State *L, int idx);
…………
LUA_API int   (lua_checkstack) (lua_State *L, int sz);
显然,这是基本的Stack的操作,别的不说,我们看看lua_State,这是什么呢?(Lstate.h 100~126)
struct lua_State {
  CommonHeader;
  lu_byte status;
  StkId top;  /* first free slot in the stack */
  StkId base;  /* base of current function */
  …………
  TValue l_gt;  /* table of globals */
  TValue env;  /* temporary place for environments */
  GCObject *openupval;  /* list of open upvalues in this stack */
  GCObject *gclist;
  struct lua_longjmp *errorJmp;  /* current error recover point */
  ptrdiff_t errfunc;  /* current error handling function (stack index) */
};
观察一下,应该是虚拟机的Stack部分。当中省略了一部分。在"The implementation of Lua 5.0"中有这么一段关于Lua的数据表示:
typedef struct {
 int t;
 Value v;
} TObject;

typedef union {
 GCObject *gc;
 void *p;
 lua_Number n;
 int b;
} Value;
这是Lua的数据表示,既然用了union那么自然,C在这个方面的类型检查并不是非常的严厉,所以一切的数据都可以装得下,且只用一个统一的指 针就好了,不用void *转换。诚然这样极为浪费存储器空间,但是,大家仔细想想,Lua的用途是什么?Lua可以用来配置C/C++程序,这就是它的 定位。数学计算,sorry,it's not my business.
继续,对于一个指向内存特定区域的GCObject是什么样的呢?(Lstate.h 135~144)
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 */
};
名字都很清晰,代码真是没的说,对于仅仅需要地址的TString,Udata和Closure用union,对于Table,Proto,UpVal,thread自然需要更多的信息。看看TString:(Lobject.h 199~207)
作者: 125.33.54.*
 
2006-11-8 16:48   回复此发言  

2 Lua 5.1.1 源代码阅读笔记
typedef union TString {
  L_Umaxalign dummy;  /* ensures maximum alignment for strings */
  struct {
    CommonHeader;
    lu_byte reserved;
    unsigned int hash;
    size_t len;
  } tsv;
} TString;
其中L_Umaxalign是
typedef LUAI_USER_ALIGNMENT_T L_Umaxalign ;
而LUAI_USER_ALIGNMENT_T是
#define LUAI_USER_ALIGNMENT_T union { double u; void *s; long l; }
明白了吗?这里是一个对齐(Alignment),对齐double,void*和long。为什么要写这三个?不是double最长吗?根据不 同的机器,不同的编译器的定义是不同的。因此同时用这三者来代表最长的数和最长的指针。这一点可以从注释中看出来,同样,为什么叫“USER”呢,因为这 因机器而定,需要USER们来修改。
TString中的dummy自然就是占位符咯。
看看Udata:(Lobject.h 215~223)
typedef union Udata {
  L_Umaxalign dummy;  /* ensures maximum alignment for `local' udata */
  struct {
    CommonHeader;
    struct Table *metatable;
    struct Table *env;
    size_t len;
  } uv;
} Udata;
Udata这种用户类型也是由Table来完成的,但是从这里暂时还看不出来Udata怎么区分Light和Heavy的。
该代码下放的Proto部分有个TValue其原型:(Lobject.h 73~75)
typedef struct lua_TValue {
  TValuefields;
} TValue;
而TValuefields:(Lobject.h 71)
#define TValuefields Value value; int tt
这就是所谓的TObject二元组(value,tag)。
对于频频出现的CommonHeader,有:(Lobject.h 43)
#define CommonHeader GCObject *next; lu_byte tt; lu_byte marked
有一个问题目前还是不清楚,即dummy要做最大的对齐,但是uv和tsv明显比dummy占用的字节要多,那么怎么对齐呢?
在展示两端赋值语句:(Lobject.h 138~141)
#define setthvalue(L,obj,x) /
 { TValue *i_o=(obj); /
  i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TTHREAD; /
  checkliveness(G(L),i_o); }
以及:(Lobject.h 119~120)
#define setnvalue(obj,x) /
  { TValue *i_o=(obj); i_o->value.n=(x); i_o->tt=LUA_TNUMBER; }
逻辑很清晰,由特定的宏函数来赋值。同样,我们看看Light和Heavy的Udata:
#define setpvalue(obj,x) /
  { TValue *i_o=(obj); i_o->value.p=(x); i_o->tt=LUA_TLIGHTUSERDATA; }

#define setuvalue(L,obj,x) /
  { TValue *i_o=(obj); /
    i_o->value.gc=cast(GCObject *, (x)); i_o->tt=LUA_TUSERDATA; /
    checkliveness(G(L),i_o); }
向用一个i_o指针指向l-value。
这里,笔者对(x)的结构不太熟悉,因而追踪setuvalue(L,obj,x)的caller:(Lapi.c 1018~1027)
LUA_API void *lua_newuserdata (lua_State *L, size_t size) {
  Udata *u;
  lua_lock(L);
  luaC_checkGC(L);
  u = luaS_newudata(L, size, getcurrenv(L));
  setuvalue(L, L->top, u);      //注:在这里
  api_incr_top(L);
  lua_unlock(L);
  return u + 1;
}
接着回头看lua_State,那个栈类型:(Lstate.h 103)
StkId top;  /* first free slot in the stack */
接着是StkId:(Lobject.h 193)
typedef TValue *StkId;  /* index to stack elements */
原来就是一个指向TValue的指针呀。cast的定义是:(Llimits.h 75)
#define cast(t, exp) ((t)(exp))
接着是checkliveness:(LObject.h 111~113)
#define checkliveness(g,obj) /
  lua_assert(!iscollectable(obj) || /
  ((ttype(obj) == (obj)->value.gc->gch.tt) && !isdead(g, (obj)->value.gc)))
其中iscollectable:(LObject.h 189)
#define iscollectable(o) (ttype(o) >= LUA_TSTRING)
还有isdead:(Lgc.h 70)
#define isdead(g,v) ((v)->gch.marked & otherwhite(g) & WHITEBITS)
另外还有GCheader:(Lobject.h 49~51)
typedef struct GCheader {
  CommonHeader;
} GCheader;
总体说来,这些结构都不难,但是组织得很好。解释一下吧:
setXvalue()中的X是类型的代称。对于非GC类型,直接设置其Value的值及tt的值;对于GC类型,要用cast将该L- Value转换成 GCObject *,接着设置tt,再检查是否活跃checkliveness(G(L),i_o)。checkliveness中 再断言iscollectable,其 gc的gcheader的tt是否与这个obj的类型一样,再者isdead。其中的iscollectable 用的方法是观察obj的tt是否大于 LUA_TSTRING,因为在那之后的都是可以收集的。 
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
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值