在Windows下编译Lua

环境

Windows 7
Visual Studio 2013
Lua 5.2.1

 

动机

因为自己比较弱,还不是非常习惯在Vim里看源代码,所以决定还是把阅读Lua源码的事情放在Windows平台上做。Source Insight不错,但是如果可以编译运行自己进行的一些小改动的话,应该能提高自己对代码的理解,因此打算在Visual Studio里读。(其实也有刚刚装了Visual Studio 2013,想尝鲜的缘故。)

官网上对于Windows下编译Lua的指南,最新的也仅是基于Visual Studio 2003了,而且是直接抛出了工程文件,参考意义是有的,但要想拥有灵活性,还是要自己动手,丰衣足食了。

 

建立项目

Lua的源代码编译后会生成三个对象:Lua库、基于该库的独立解释器以及字节码编译器。所以,在solution中,我们也要建立三个project与他们对应。在这里,暂且称整个solution为lua_vs11,三个project分别为lua_lib、lua、luac,类型都可以选择C++的空项目。

还没有读源代码,这里先按照网上的说法对源代码进行分配,lua.c放在lua项目下,luac.c放在luac项目下,其余的全部都是lua库的代码,扔在lua_lib下。

 

生成设置

首先,我们要设置lua以及luac依赖于lua_lib,这样可以保证lua_lib最先进行编译。(项目上右键,“项目依赖项”)

其次,我们要让lua以及luac中包含lua头文件的预处理指令正常得到展开。这里采用比较土的方法,是把lua_lib加入到lua以及luac的预处理器搜索目录中。(项目属性,VC++目录,包含目录)

最后,我们要让lua以及luac能够在链接时正常链接到lua_lib上,这里要做三件事:

1.我们要修改lua_lib项目的生成目标为静态库。(项目属性,常规,配置类型)
2.我们要让lua以及luac链接lua_lib.lib库。(项目属性,链接器,输入,附加依赖项)
3.我们要让lua以及luac搜索lua_lib.lib所在目录(即我们的生成目录),以正确的找到该库。(项目属性,链接器,常规,附加库目录)

好了,按下F6生成解决方案吧。: )

对Visual Studio不是很熟悉,用的可能是比较土的方法,如果你有更加好的建议,请一定留言。 : P

 

 

Lua源码剖析之Nil类型与nil值的实现

 

首先,要搞明白Nil类型和nil值的实现原理,就要搞明白Lua里变量、值、类型三者之间的关系和实现方式,搞明白了以后,Nil不过是其中一种例子而已。

 

Lua is a dynamically typed language. This means that variables do not have types; only values do. There are no type definitions in the language. All values carry their own type.

All values in Lua are first-class values. This means that all values can be stored in variables, passed as arguments to other functions, and returned as results.

–Lua Reference Manual

也即,Lua里变量没有类型,值才有类型,并且每一个值都携带了他的类型信息。同时,Lua中的所有值都是第一类值,意味着所有值都可以被存储在变量中、作为参数传递给其他函数以及作为返回值。

 

落实到代码上,具体实现如下:

 

typedef struct lua_TValue TValue;

struct lua_TValue {
TValuefields;
};

#define TValuefields    Value value_; int tt_

 

“值”在实现中用TValue类型的变量来存储。把以上语句手工展开一下,如下:

 

struct TValue {
    Value value;
    int tt_
};

这里value是具体的值(或对具体值的引用),tt_是该变量的类型。也即实现了上文所说的“值才有类型,并且每一个值都携带了他的类型信息。”

通过追踪若干个宏以后,可以得知tt_中存储的是例如LUA_TNIL、LUA_TBOOLEAN        之类的值,为Lua中的8种类型,此外还有一个NUMTAGS,具体作用未知。

 

其实到这里已经知道Nil类型是如何实现的了,nil值如何实现还有待发掘。不过既然已经挖到了这一层,不妨继续把Value类型也深挖一下:

 

union Value {
GCObject *gc;    /* collectable objects */
void *p;         /* light userdata */
int b;           /* booleans */
lua_CFunction f; /* light C functions */
numfield         /* numbers */
};

为了节省空间,Value是定义成一个联合体的,由于可以被回收的对象,需要包含更多的信息,所以这里用一个*gc指针引用。其他的四种类型就用C基本类型定义了,其中值得注意的是numfield,他是数字类型的实现,以前提到过Lua里数字是用双精度浮点数实现的,如果需要修改的话应该就是在这里下手,numfield定义如下:

 

#define numfield    lua_Number n;    /* numbers */

typedef LUA_NUMBER lua_Number;

#define LUA_NUMBER  double

 

其中第一个宏定义的作用我可以理解,但我还不太明白为什么中间要加上第二个宏定义,也许要等以后通读代码后才能找到答案。

GCObject的定义这里就不分析了,等到以后研究垃圾回收机制的时候再深入研究。

 

扯远了,回到正题,Nil类型如何实现已经找到,应该就是通过给_tt赋值LUA_TNIL来实现,那么nil值如何实现呢,答案在table的getint函数(通过数字索引返回表项的函数)中:

 

/*
** search function for integers
*/

const TValue *luaH_getint (Table *t, int key) {
   /* (1 <= key && key <= t->sizearray) */
   if (cast(unsigned int, key-1) < cast(unsigned int, t->sizearray))
     return &t->array[key-1];
   else {
     lua_Number nk = cast_num(key);
     Node *n = hashnum(t, nk);
     do {  /* check whether `key' is somewhere in the chain */
       if (ttisnumber(gkey(n)) && luai_numeq(nvalue(gkey(n)), nk))
         return gval(n);  /* that's it */
       else n = gnext(n);
     } while (n);
     return luaO_nilobject;
   }
}

该函数的流程是:首先在table的array部分查找,若不在,则在hash表部分查找,若还不在,则返回luaO_nilobject对象,毫无疑问,这就是nil值。定义如下:

 

/* 
** (address of) a fixed nil value
*/
#define luaO_nilobject      (&luaO_nilobject_)

通过注释可以知道,在Lua中,所有nil值实际上都是同一个(fixed)对象——luaO_nilobject_

该对象定义如下:

 

LUAI_DDEF const TValue luaO_nilobject_ = {NILCONSTANT};

#define NILCONSTANT {NULL}, LUA_TNIL

展开则得:

 

LUAI_DDEF const TValue luaO_nilobject_ = {
    {NULL},
    LUA_TNIL
};

也即,一个value为{NULL},tt_为LUA_TNIL的TValue,这也印证了之前关于Nil类型实现方式的猜测。

 

至此,Nil类型和nil值的实现方法都已经明晰:

Nil类型:tt_ = LUA_TNIL

nil值:为同一个Nil类型的对象luaO_nilobject_

 

table类型对不存在的表项进行索引时返回nil值的机制也已经明了:

array部分和hash表部分都不存在该索引时则返回&luaO_nilobject_