Lua
除了是一门扩展的言语外,还是一门“胶水语言”。最著名的就是作为游戏的脚本开发。
这里主要说lua
怎样与c交互。Lua
和c
语言通信的主要方法是一个无所不在的虚拟栈。几乎所有的API
调用都会操作这个栈上的值。所有的数据交换,无论是Lua
到C
语言或C
语言到Lua
都通过这个栈来完成。栈可以解决Lua
和C
语言之间存在的两大差异,第一种差异是Lua
使用垃圾收集,而C
语言要求显式地释放内存;第二种是Lua
使用动态类型,而C
语言使用静态类型。
环境搭建
1 | lua_State *L = luaL_newstate();//创建一个新的环境 |
luaL_newstate
用c运行库的内存分配函数。lua_newstate
可自定义内存分配函数。
头文件lua.h
定义了lua
提供的基础函数,包括创建lua
环境、调用lua
函数(如lua_pcall
)、读写lua
环境中全局变量,以及注册供lua
调用的新函数等。Lua.h
中定义所有内容都有一个lua_
前缀。
头文件lauxlib.h
定义了辅助库(auxiliary library,auxlib)
提供的函数。它的所有定义都以luaL_
开头(如luaL_loadbuffer
)。辅助库是一个使用lua.h
中API
编写出的一个较高的抽象层。Lua
的所有标准库编写都用到了辅助库。
Lua脚本的编译执行是相互独立的,在不同的线程上执行。通过luaL_newstate()函数可以申请一个虚拟机,返回指针类型lua_State。今后其他所有Lua Api函数的调用都需要此指针作为第一参数,用来指定某个虚拟机。所以lua_State代表一个lua虚拟机对像,luaL_newstate()分配一个虚拟机。lua类库管理着所有的虚拟机。销毁指定虚拟机的所有对像(如果有垃圾回收相关的无方法则会调用该方法)并收回所有由该虚拟机动态分配产生的内存,在有些平台下我们不需要调用此函数,因为当主程序退出时,资源会被自然的释放掉,但是但一个长时间运行的程序,比如后台运行的web服务器,需要立即回收虚拟机资源以避免内存过高占用。
Lua虚拟机相关的数据结构与栈
解释器要做的就是模拟计算机的执行,这主要分为以下两大块。
CPU
:用于指令的执行。
内存:用于数据的存储。
指令执行的部分在lua虚拟机(lua虚拟机工作流程)大体介绍过,即解释器分析Lua
文件之后生成Proto
结构体,最后到luaV_execute
函数中依次取出指令来执行。
而“内存”部分,在Lua
解释器中就存放在Lua
栈中。Lua
中也是把栈的某一个位置称为寄存器(非真CPU
中的寄存器)。
每个Lua
虚拟机对应一个lua_State
结构体,它使用TValue
数组来模拟栈,其中包括几个与找相关的成员。stack
:栈数组的起始位置。base
: 当前函数栈的基地址 。top
: 当前栈的下一个可用位置 。
这些成员的初始化操作在stack_init
函数中完成。
1 | static void stack_init (lua_State *L1, lua_State *L) { |
再看看lua_State
。
1 | struct lua_State { |
lua_State
里面存放的是一个Lua
虚拟机的全局状态,当执行到一个两数时,需要有对应的数据结构来表示函数相关的信息。这个数据结构就是CallInfo
,这个结构体中同样有top
、base
这两个与栈相关的成员。
1 | /* |
无论函数怎么执行,有多少函数,最终它们引用到的栈都是当前Lua
虚拟机的栈。 这好比一个操作系统中的进程无论有多少,最终引用的内存实际上都还是由操作系统内核来管理的。
在lua_State
结构体中,有一个base_ci
的CallInfo
数组,存储的就是CallInfo
的信息。而另一个ci
成员,指向的就是当前函数的CallInfo
指针。
在调用函数之前,一般会调用luaD_precall
函数,它主要完成如下几个操作 。
(1)保存当前虚拟机执行的指令
savedpc
到当前CallInfo
的savedpc
中。此处保存下来是为了后面调用完毕之后恢复执行
(2)分别计算出待调用函数的base
、top
值,这些值的计算依赖于函数的参数数量
(3)从lua_State
的base_ci
数组中分配一个新的CallInfo
指针,存储前面两步计算出来的信息,切换到这个函数中准备调用。
可以看到,lua_State
结构体中的top
、base
指针是与函数执行相关的变量,在函数执行前后都会有所变化。
需要注意的是,前后调用的函数中Lua栈的大小是有限的,同时CallInfo数组的大小也是有限的。 栈的使用和函数的嵌套层次都不能过多,以防这些资源、用尽了。 这就好比操作系统内核不可能无限制新建进程,也不可能无限制分配内存,资源总是有限的。