Q:什么是Lua的虚拟栈?
A:C与Lua之间通信关键内容在于一个虚拟的栈。差点儿全部的调用都是对栈上的值进行操作,全部C与Lua之间的数据交换也都通过这个栈来完毕。另外,你也能够使用栈来保存暂时变量。
每个与Lua通信的C函数都有其独有的虚拟栈。虚拟栈由Lua管理。
栈的使用攻克了C和Lua之间两个不协调的问题:第一。Lua会自己主动进行垃圾收集,而C要求显式的分配存储单元。两者引起的矛盾。第二。Lua中的动态类型和C中的静态类型不一致引起的混乱。
Q:虚拟栈索引?
A:正索引的顺序为元素入栈的先后顺序。栈中的第一个元素(也就是第一个被入栈的元素)索引为1。第二个元素索引为2,以此类推。我们也能够使用负索引,以栈顶作为索引的參照来存取元素。栈顶元素的索引为-1。其前面的一个元素索引为-2,以此类推。正负索引各有其适用的场合。灵活应用,简化编码。
Q:第一个样例?
A:一个简单的Lua解释器。
#include <stdio.h>
#include <string.h>
/* 定义对Lua的基本操作的函数,当中定义的全部函数都有"lua_"前缀。
* 比如调用Lua函数(如"lua_pcall()"),获取以及赋值Lua的全局变量,注冊函数供Lua调用,等等。
*/
#include <lua.h>
/* 定义"auxiliary library",当中定义的全部函数都有"luaL_"前缀。
* "auxiliary library"使用lua.h中提供的基本API实现了更高一层抽象方法的实现。
* 全部的Lua标准库都使用了auxiliary library。
*/
#include <lauxlib.h>
/* 定义打开各种库的函数。
*/
#include <lualib.h>
int main(void)
{
char buff[256] = {0};
int error = 0;
lua_State *L = luaL_newstate(); // 创建Lua状态机。
luaL_openlibs(L); // 打开Lua状态机"L"中的全部Lua标准库。
while (fgets(buff, sizeof(buff), stdin) != NULL) { // 获取用户输入。
/* "luaL_loadbuffer()",编译用户输入为Lua的"chunk"并将其入栈。
* "line"是"chunk"的名字。用于调试信息和错误消息。
* "lua_pcall()",以保护模式执行"chunk"并将其从栈顶弹出。
* 两个函数均是在成功时返回"LUA_OK"(实际的值是0),
* 失败时返回错误码,并将错误信息入栈。
*/
error = luaL_loadbuffer(L, buff, strlen(buff), "line") || lua_pcall(L, 0, 0, 0);
if (error) {
fprintf(stderr, "%s", lua_tostring(L, -1)); // 从栈顶取出错误打印信息。
lua_pop(L, 1); // 弹出栈顶的错误信息。
}
}
lua_close(L); // 关闭Lua状态机。
return 0;
}
prompt> gcc main.c -llua -ldl -lm
prompt> ./a.out
print("Hello World!")
Hello World!
Q:怎样将元素入栈与出栈?
A:Lua中的数据类型与C中的并不一一相应,对于不同的数据类型,须要不同的入栈函数。
// 将"b"作为一个布尔值入栈。
void lua_pushboolean(lua_State *L, int b)
/* 将C函数"fn"以及其在虚拟栈上关联的"n"个值作为"Closure"入栈。
* "n"最大为255,第一个被关联的值首先入栈。栈顶是最后一个被关联的值,
* 这些值会在函数调用成功后被出栈。
*/
void lua_pushcclosure(lua_State *L, lua_CFunction fn, int n)
// 将C函数"f"作为函数入栈。
内部实际调用"lua_pushcclosure(L, f, 0)"。
void lua_pushcfunction(lua_State *L, lua_CFunction f)
/* 将一个被格式化后的字符串入栈。函数返回这个字符串的指针。
* 与C语言中的"sprintf()"相似。其差别在于:
* 1、不须要为结果分配空间。
* 其结果是一个Lua字符串,由Lua来关心其内存分配(同一时候通过垃圾收集来释放内存)。
* 2、"fmt"不支持符号、宽度、精度。
仅仅支持:
* "%%": 字符'%'。
* "%s": 带零终止符的字符串,没有长度限制。
* "%f": "lua_Number"(Lua中的浮点数类型)。
* "%L": "lua_Integer"(Lua中的整数类型)。
* "%p": 指针或是一个十六进制数。
* "%d": "int"。
* "%c": "char"。
* "%U": 用"long int"表示的UTF-8字。
*/
const char *lua_pushfstring(lua_State *L, const char *fmt, ...)
/* 将长度为"len"。非字面形式的字符串"s"入栈。
* Lua对这个字符串做一个内部副本(或是复用一个副本),
* 因此"s"处的内存在函数返回后,能够释放掉或是立马重用于其他用途。
* 字符串内能够是随意二进制数据。包括'\0'。函数返回内部副本的指针。
*/
const char *lua_pushlstring(lua_State *L, const char *s, size_t len)
// 将字面形式的字符串"s"入栈,函数自己主动给出字符串的长度。返回内部副本的指针。
const char *lua_pushliteral(lua_State *L, const char *s)
/* 将以'\0'结尾的字符串"s"入栈。
* Lua对这个字符串做一个内部副本(或是复用一个副本)。
* 因此"s"处的内存在函数返回后,能够释放掉或是立马重用于其他用途。
* 函数返回内部副本的指针。
假设"s"为"NULL",将"nil"入栈并返回"NULL"。
*/
const char *lua_pushstring(lua_State *L, const char *s)
// 等价于"lua_pushfstring()"。只是是用"va_list"接收參数,而不是用可变数量的实际參数。
const char *lua_pushvfstring(lua_State *L, const char *fmt, va_list argp)
// 将全局环境入栈。
void lua_pushglobaltable(lua_State *L)
// 将值为"n"的整数入栈。
void lua_pushinteger(lua_State *L, lua_Integer n)
/* 将一个轻量用户数据"p"入栈。
* 用户数据是保留在Lua中的C值。轻量用户数据表示一个指针"void*"。
* 它像一个数值,你不须要专门创建它,它也没有独立的元表,
* 并且也不会被收集(由于从来不须要创建)。仅仅要表示的C地址同样。两个轻量用户数据就相等。
*/
void lua_pushlightuserdata(lua_State *L, void *p)
// 将空值入栈。
void lua_pushnil(lua_State *L)
// 将一个值为"n"的浮点数入栈。
void lua_pushnumber(lua_State *L, lua_Number n)
// "L"表示的线程入栈。假设这个线程是当前状态机的主线程的话。返回1。
int lua_pushthread(lua_State *L)
// 将虚拟栈上索引"index"处的元素的副本入栈。
void lua_pushvalue(lua_State *L, int index)
相应的出栈函数就非常easy了,仅仅有一个lua_pop()
。
// 从虚拟栈中弹出"n"个元素。
void lua_pop(lua_State *L, int n)
Q:怎样检查虚拟栈中元素的类型?
A:
// 假设栈中索引"index"处的元素为"bool"类型,则返回1,否则返回0。
int lua_isboolean(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个C函数,则返回1。否则返回0。
int lua_iscfunction(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个C函数或是一个Lua函数,则返回1。否则返回0。
int lua_isfunction(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个整数。则返回1,否则返回0。
int lua_isinteger(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个轻量级的"userdata",则返回1,否则返回0。
int lua_islightuserdata(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个"nil",则返回1。否则返回0。
int lua_isnil(lua_State *L, int index
// 假设"index"是一个无效索引时,返回1,否则返回0。
int lua_isnone(lua_State *L, int index)
// 假设"index"是一个无效索引或者"index"处的元素是一个"nil",则返回1,否则返回0。
int lua_isnoneornil(lua_State *L, int index)
/* 假设栈中索引"index"处的元素是一个数值或者是一个能够转换为数值的字符串,
* 则返回1。否则返回0。
*/
int lua_isnumber(lua_State *L, int index)
/* 假设栈中索引"index"处的元素是一个字符串或者是一个能够转换为字符串的数值。
* 则返回1。否则返回0。
*/
int lua_isstring(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个"table"。则返回1,否则返回0。
int lua_istable(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个线程。则返回1,否则返回0。
int lua_isthread(lua_State *L, int index)
// 假设栈中索引"index"处的元素是一个"userdata",则返回1,否则返回0。
int lua_isuserdata (lua_State *L, int index)
// 假设栈中的"coroutine"能够被挂起,则返回1,否则返回0。
int lua_isyieldable(lua_State *L)
/* 返回栈中索引"index"处元素的类型。这些类型在"lua.h"中定义,例如以下:
* #define LUA_TNONE (-1) // 无效
* #define LUA_TNIL 0 // "nil"
* #define LUA_TBOOLEAN 1 // "bool"
* #define LUA_TLIGHTUSERDATA 2 // 轻量级"userdata"
* #define LUA_TNUMBER 3 // 数值
* #define LUA_TSTRING 4 // 字符串
* #define LUA_TTABLE 5 // "table"
* #define LUA_TFUNCTION 6 // 函数
* #define LUA_TUSERDATA 7 // "userdata"
* #define LUA_TTHREAD 8 // 线程
*/
int lua_type(lua_State *L, int index)
// 返回"tp"表示的类型的名字。"tp"是"lua_type()"的返回值之中的一个。
const char *lua_typename(lua_State *L, int tp)
Q:怎样转换虚拟栈中元素的类型?
A:
// 将栈中"index"处的元素转换为C中的"bool"值返回。
int lua_toboolean(lua_State *L, int index)
// 将栈中"index"处的元素转换为一个C函数返回。
指定的元素必须是一个C函数,否则返回"NULL"。
lua_CFunction lua_tocfunction(lua_State *L, int index)
/* 将栈中"index"处的元素转换为一个整数返回。
* 指定的元素必须是一个整数或是一个能够被转换为整数的数字或字符串,否则返回0。
* 假设"isnum"非"NULL"。"*isnum"会被赋值为操作是否成功的"bool"值。
*/
lua_Integer lua_tointegerx(lua_State *L, int index, int *isnum)
// 内部调用"lua_tointegerx(L, index, NULL)"。
lua_Integer lua_tointeger(lua_State *L, int index)
/* 将栈中"index"处的元素转换为一个C字符串并将其指针返回。
* 假设"len"非"NULL","*len"将获得字符串的长度。
* 指定元素必须是一个字符串或是一个数字。否则返回返回"NULL"。
* 假设指定元素是一个数字,函数会将元素的类型转换为字符串。
* 返回的字符串结尾包括'\0',而在字符串中同意包括多个'\0'。
* 函数返回的字符串应马上转存,否则有可能被Lua垃圾回收器回收。
*/
const char *lua_tolstring(lua_State *L, int index, size_t *len)
/* 将栈中"index"处的元素转换为一个浮点数返回。
* 指定的元素必须是一个数字或是一个可被转换为数字的字符串。否则返回0。
* 假设"isnum"非"NULL","*isnum"会被赋值为操作是否成功的"bool"值。
*/
lua_Number lua_tonumberx(lua_State *L, int index, int *isnum)
// 内部调用"lua_tonumberx(L, index, NULL)"。
lua_Number lua_tonumber(lua_State *L, int index)
/* 将栈中"index"处的元素转换为一个C指针返回。
* 指定的元素必须是一个"userdata"。"table",线程或是一个函数,否则返回"NULL"。
*/
const void *lua_topointer(lua_State *L, int index)
// 内部调用"lua_tolstring(L, index, NULL)"。
const char *lua_tostring(lua_State *L, int index)
/* 将栈中"index"处的元素转换为一个Lua线程返回。
* 指定的元素必须是一个线程,否则返回"NULL"。
*/
lua_State *lua_tothread(lua_State *L, int index)
/* 栈中"index"处的元素假设是一个全然"userdata",则返回其内存地址的指针。
* 假设是一个轻量级"userdata",则返回其存储的指针。
*/
void *lua_touserdata(lua_State *L, int index)
Q:一些维护虚拟栈的方法?
A:
/* int lua_gettop(lua_State *L)
* 返回栈顶元素的索引。
* 由于栈中元素的索引是从1開始编号的。所以函数的返回值相当于栈中元素的个数。
* 返回值为0表示栈为空。
*/
lua_gettop(L); // 返回栈中元素的个数。
/* void lua_settop(lua_State *L, int index)
* 设置栈顶为索引"index"指向处。
* 假设"index"比"lua_gettop()"的值大。那么多出的新元素将被赋值为"nil"。
*/
lua_settop(L, 0); // 清空栈。
/* void lua_remove(lua_State *L, int index)
* 移除栈中索引"index"处的元素,该元素之上的全部元素下移。
*/
/* void lua_insert(lua_State *L, int index)
* 将栈顶元素移动到索引"index"处。索引"index"(含)之上的全部元素上移。
*/
/* void lua_replace(lua_State *L, int index)
* 将栈顶元素移动到索引"index"处。(相当于覆盖了索引"index"处的元素)
*/
一个”dump”整个堆栈内容的样例。
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
static void stackDump(lua_State *L)
{
int i = 0;
int top = lua_gettop(L); // 获取栈中元素个数。
for (i = 1; i <= top; ++i) // 遍历栈中每个元素。
{
int t = lua_type(L, i); // 获取元素的类型。
switch(t)
{
case LUA_TSTRING: // strings
printf("'%s'", lua_tostring(L, i));
break;
case LUA_TBOOLEAN: // bool
printf(lua_toboolean(L, i) ? "true" : "false");
break;
case LUA_TNUMBER: // number
printf("%g", lua_tonumber(L, i)); // %g,自己主动选择%e或%f表示数值。
break;
default: // other values
printf("%s", lua_typename(L, t)); // 将宏定义的类型码转换为类型名称。
break;
}
printf(" "); // put a separator
}
printf("\n");
}
int main(void)
{
lua_State *L = luaL_newstate(); // 创建Lua状态机。
// 向虚拟栈中压入值。
lua_pushboolean(L, 1); // true
lua_pushnumber(L, 10); // 10
lua_pushnil(L); // nil
lua_pushstring(L, "hello"); // "hello"
stackDump(L); // true 10 nil 'hello'
lua_pushvalue(L, -4); // 将索引-4处的值的副本入栈。
stackDump(L); // true 10 nil 'hello' true
lua_replace(L, 3); // 将栈顶元素移动到索引3处。并覆盖原先的元素。
stackDump(L); // true 10 true 'hello'
lua_settop(L, 6); // 将栈顶设置为索引6处,多出来的新元素被赋值为"nil"。
stackDump(L); // true 10 true 'hello' nil nil
lua_remove(L, -3); // 移除索引-3处的元素,其上全部元素下移。
stackDump(L); // true 10 true nil nil
lua_settop(L, -5); // 将栈顶设置为索引-5处。
stackDump(L); // true
lua_close(L); // 关闭Lua状态机。
return 0;
}
附加:
1、Lua库未定义不论什么全局变量,它全部的状态都保存在动态结构lua_State
中,并且指向这个结构的指针作为全部Lua函数的一个參数。这种实现方式使得Lua能够重入(reentrant)。并且为在多线程中的使用中作好准备。
2、假设你是在C++的代码中使用Lua,别忘了,
extern "C" {
#include <lua.h>
}
3、Lua中的字符串能够不以'\0'
作为结束符。这样,字符串中能够包括随意的二进制(甚至是'\0'
),字符串的长度由明白的长度指定。
4、在lua_pushlstring()
、lua_pushliteral()
以及lua_pushstring()
中。Lua不保存字符串(变量)指针。因此当这些函数返回时。你就能够改动你的字符串了。
5、对于入栈是否有栈空间的情况,你须要自己推断,别忘了如今你是一个C程序猿。当Lua启动或者不论什么Lua调用C的时候,虚拟栈中至少有20个空间(在”lua.h”中LUA_MINSTACK
定义),这对于普通情况下够用了,所以一般不用考虑。但有时候确实须要很多其他的栈空间(比方调用一个不定參数的函数),此时你须要使用lua_checkstack
检查栈空间的情况。
/* 确保堆栈上至少有"n"个额外空位。假设不能把堆栈扩展到相应的尺寸,函数返回"false"。
* 失败的原因包括将把栈扩展到比固定最大尺寸还大(至少是几千个元素)或分配内存失败。
* 这个函数永远不会缩小堆栈,假设堆栈已经比须要的大了,那么就保持原样。
*/
int lua_checkstack(lua_State *L, int sz)
6、遍历一个”table”时。不要将lua_tolstring()
作用在”key”上,这样会导致lua_next()
无法正常执行。
7、在寻常的编码中,对于执行失败时会返回0
的lua_to*()
类别的函数。我们最好先使用lua_is*()
类别的函数推断參数的类型,之后再使用lua_to*()
类别的函数对參数进行转换;而对于执行失败时会返回NULL
的lua_to*()
类别的函数,我们能够直接使用lua_to*()
类别的函数直接对參数进行转换。推断函数的返回值非NULL
与否,就能推断转换是否成功。
8、lua_pop()
就是通过lua_settop()
实现的(在”lua.h”中定义),
#define lua_pop(L,n) lua_settop(L, -(n)-1)
9、下面操作对于虚拟栈没有不论什么影响,
lua_settop(L, -1); /* set top to its current value */
lua_insert(L, -1); /* move top element to the top */
lua_replace(L, -1); /* replace top element by the top element */