Lua 学习 chapter27
目录
- 前言
- 第一个示例
- lua堆栈操作
- 处理应用代码中的错误
- 内存分配
只有疯狂过,你才知道自己究竟能不能成功。
前言
lua是一种嵌入式语言,这就意味着lua并不是一个独立运行的应用,而是一个库,它可以链接到其它应用程序,将lua的功能融入到这些应用。
由于lua存在解释器(可执行的lua),所以我们可以独立的使用它,这个解释器是由lua标准库实现的独立解释器,它负责与用户交互,将用户的文件和字符串传递给lua标准库,由标准库完成主要工作。
因为能被当作ku来扩展某个应用程序,所以lua是一个嵌入式语言。同时,使用了lua语言的程序也可以在lua环境中注册新的函数,比如用c语言实现的函数,从而增加一些无法直接用lua语言编写的功能,因此lua也是一种可扩展的语言。
上述的两种对lua语言的定位,分别对应c语言和lua语言之间的两种交互方式。在第一种形式中,c语言拥有控制权,而lua语言被用作库,这种交互形式中c代码被称为应用代码。在第二种中,lua语言拥有控制权,而c语言被用作库,因此c代码被称为库代码。应用代码和库代码都是用相同的API与lua语言通信,这些API被称为C API。
C API是一个函数、常量和类型组成的集合,有了它,c语言代码就能与lua语言交互。C API包括读写lua全局变量的函数、调用lua函数的函数、运行lua代码段的函数以及注册c函数(以便于其后可被lua代码调用)的函数等。通过调用C API,C代码几乎可以做lua代码能够做的所有事情。
C API遵循C语言的操作模式,与lua模式有很大的区别。所以在c的时候可能会抛弃易用性,但是在效率上,c代码可能会高一些。
第一个示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<stdio.h>
#include<string.h>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}
int main(void)
{
char buff[256];
int error;
lua_State* L = luaL_newstate();//打开lua
luaL_openlibs(L);//打开标准库
<span class="k">while</span> <span class="p">(</span><span class="n">fgets</span><span class="p">(</span><span class="n">buff</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">buff</span><span class="p">),</span> <span class="n">stdin</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
<span class="n">error</span> <span class="o">=</span> <span class="n">luaL_loadstring</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="n">buff</span><span class="p">)</span> <span class="o">||</span> <span class="n">lua_pcall</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="n">error</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">fprintf</span><span class="p">(</span><span class="n">stderr</span><span class="p">,</span> <span class="s">"%s</span><span class="se">n</span><span class="s">"</span><span class="p">,</span> <span class="n">lua_tostring</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">));</span>
<span class="n">lua_pop</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span><span class="c1">//从栈中弹出错误信息</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">lua_close</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
}
– 测试输入
qewqr
[string "qewqr…"]:2: syntax error near <eof>
print("hello")
hello
local t = {}
t.a = 5
[string "t.a = 5…"]:1: attempt to index a nil value (global 't')
t= {}
t.a = 5
print(t.a)
5
接下来我们来熟悉一下各种头文件的提供了那些函数,其中头文件lua.h声明了lua提供的基础函数,其中包括创建新的lua环境的函数,调用lua函数的函数、读写环境中的全局变量的函数,以及注册供lua语言调用的新函数的函数。lua.h中声明的所有的内容都有一个前缀lua_(eg:lua_pcall).
头文件lauxlib.h声明了辅助库(auxiliary library, auxlib)所提供的的函数,其中所有的声明均以luaL_开头(eg:luaL_loadstring)。辅助库使用lua.h提供的基础API来提供更高层次的抽象,特别是对标准库用到的相关机制进行抽象。
lua标准库没有定义任何c语言全局变量,它将所有的状态都保存在动态的结构体lua_State中,lua中的所有函数都接收一个纸箱该结构的指针作为参数。这种设计使得lua是可重入的,并且可以直接用于编写所线程代码。
函数luaL_newstate用于创建一个新的lua状态。当它创建一个新的状态时,新的环境中没有包含预定一个的函数,甚至连print都没有。为了保持lua语言的精炼,所有的标准库都被组织成不同的包,这样我们在不需要使用某些包的时候可以忽略它们。头文件lualib.h中声明了用于打开这些库的函数。函数luaL_openlibs用于打开所有的标准库。
当创建好一个状态并在其中加载了标准库之后,就可以处理用户的输入了。程序会首先调用函数luaL_loadstring来编译用户输入的每一行内容。如果没有错误,则返回零,并向栈中压入编译后得到的函数。然后,程序调用函数lua_pcall从栈中弹出编译后的函数,并以保护模式运行。如果没有发生错误,pcall一样返回零,如果发生错误,这两个函数都会像栈中压入一条错误信息。然后我们可以通过lua_tostring来获取错误信息。
在其中有一个lua_pop函数,该函数表示从当前lua状态栈中弹出几个元素,如lua_pop( pLua, 2 )表示从栈顶弹出2个元素,当第二个参数填入-1时弹出所有元素即lua_pop( pLua, -1 ).
lua堆栈操作
lua和c之间的通信主要组件是无处不在的虚拟栈,几乎所有的API调用都是在操作这个栈中的值,lua与c之间的所有数据交换都是通过这个栈完成的。此外,还可以利用栈保存中的结果。
在对lua栈操作的时候,当循环向栈中压入元素的时候,需要调用函数lua_checkstack来检查栈中是否有足够的空间。
C API提供了一系列lua_is*的函数,其中*可以是任意一种lua数据类型。这些函数包括lua_isnil,lua_isnumber,lua_isstring和lua_istable.lua_type返回栈中元素的类型,包含:LUA_TSTRING,LUA_TBOOLEAN,LUA_TNUMBER,LUA_TSTRING等。
针对于lua堆栈的操作。
C API使用索引(index)来引用栈中的元素。。第一个被压如栈的元素索引为1,第二个被压入的元素索引为2,-1表示栈顶元素,-2表示在它之前被压入栈的元素。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static void stackDump(lua_State* L) {
int i;
int top = lua_gettop(L); //栈深度
for (int i = 0; i <= top; i++)
{
int t = lua_type(L, i);
switch (t)
{
case LUA_TSTRING: {
printf("%s", lua_tostring(L, i));
break;
}
case LUA_TBOOLEAN: {
printf(lua_toboolean(L, i) ? "true" : "false");
break;
}
case LUA_TNUMBER: {
printf("%g", lua_tonumber(L, i));
break;
}
default:
printf("%s", lua_typename(L, t));
break;
}
printf(" ");
}
printf("n");
}
static void test() {
lua_State* L = luaL_newstate();
<span class="n">lua_pushboolean</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="n">lua_pushnumber</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">10</span><span class="p">);</span>
<span class="n">lua_pushnil</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
<span class="n">lua_pushstring</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="s">"hello"</span><span class="p">);</span>
<span class="n">stackDump</span><span class="p">(</span><span class="n">L</span><span class="p">);</span><span class="c1">//true 10 nil hello</span>
<span class="n">lua_pushvalue</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">4</span><span class="p">);</span><span class="c1">//将指定索引的值压到栈顶</span>
<span class="n">stackDump</span><span class="p">(</span><span class="n">L</span><span class="p">);</span> <span class="c1">//true 10 nil hello true</span>
<span class="n">lua_replace</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span><span class="c1">//pop栈顶元素,并将pop的值设置到指定索引</span>
<span class="n">stackDump</span><span class="p">(</span><span class="n">L</span><span class="p">);</span><span class="c1">// true 10 true hello</span>
<span class="n">lua_settop</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">6</span><span class="p">);</span><span class="c1">//设置栈中元素个数,0的话清空栈,大于原来个数补nil</span>
<span class="n">stackDump</span><span class="p">(</span><span class="n">L</span><span class="p">);</span><span class="c1">//true 10 true hello nil nil</span>
<span class="n">lua_rotate</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span><span class="c1">//将指定元素向栈顶转动n个位置,并把栈顶元素补充过来</span>
<span class="n">stackDump</span><span class="p">(</span><span class="n">L</span><span class="p">);</span><span class="c1">//true 10 nil true hello nil</span>
<span class="n">lua_remove</span><span class="p">(</span><span class="n">L</span><span class="p">,</span> <span class="o">-</span><span class="mi">3</span><span class="p">);</span><span class="c1">//移除指定位置的值</span>
<span class="n">stackDump</span><span class="p">(</span><span class="n">L</span><span class="p">);</span><span class="c1">//true 10 nil hello nil</span>
<span class="n">lua_close</span><span class="p">(</span><span class="n">L</span><span class="p">);</span>
}
处理应用代码中的错误
1
2
3
4
int secure_foo(lua_State *L){
lua_pushcfucntion(L, foo);
return(lua_pcall(L,0,0,0) == 0)
}
内存分配
lua语言核心对内存不进行任何假设,它既不会调用malloc也不会调用realloc来分配内存。相反lua语言核心只会通过一个分配内存函数来分配和释放内存,当用户创建状态时必须提供函数。
luaL_newstate是一个默认分配函数创建Lua状态的辅助函数。该默认分配函数使用了c语言标准库的标准函数malloc-realloc-freee,对于大多数程序来岁,这几个函数够用了。但是要完全控制lua的内存分配也很容易,使用原始的lua_newstate来创建我们自己的lua状态即可。
1
lua_State *lua_newstate(lua_Alloc f, void *ud);
该函数有两个参数:一个是分配函数,另一个是用户数据。用这种方式创建的lua状态会通过调用f完成所有的内存分配和释放,甚至结构lua_State也是由f分配的。
1
typedef void *(*lua_Alloc)(void *ud, void *ptr, size_t osize,size_t nsize);
第一个参数始终为lua_newstate所提供的的用户数据;第二个参数正是被(重)分配或者释放的块的地址;第三个参数是原始块的大小;最后一个参数请求块大小。如果ptr不是NULL,lua会保证其之前分配的大小就是osize(如果是NULL,那么这个块之前的大小肯定是零,所以lua使用osize来存放某些调试信息)。
1
2
3
4
5
6
7
8
9
void *l_alloc(void *ud,void *ptr,size_t osize,size_t nsize){
(void) ud;(void)osize;
if(nsize ==0){
free(ptr);
return NULL;
}
else
return realloc(ptr, nsize);
}
<hr style="visibility: hidden;"/>
<hr style="visibility: hidden;"/>