Lua 学习 chapter27

12k 词

目录

  1. 前言
  2. 第一个示例
  3. lua堆栈操作
  4. 处理应用代码中的错误
  5. 内存分配

只有疯狂过,你才知道自己究竟能不能成功。

前言

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">&#34;%s</span><span class="se">n</span><span class="s">&#34;</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">&#34;hello&#34;</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;"/>