lua是一个比较简单的脚本语言,我们可以用C/C++对它进行扩展。当我们使用C++与lua进行交互的时候,因为两种语言的数据结构是有很大的不同的,所以这个时候我们是通过虚拟栈来进行信息的交换的。
如何操作虚拟栈
可以把虚拟栈看做一种数据容器,它可以存放lua中的任何数据类型包括函数。
- luac
//将栈的索引转换为正索引 LUA_API int (lua_absindex) (lua_State *L, int idx); //获取栈顶的索引(即栈的大小) LUA_API int (lua_gettop) (lua_State *L); //设置新的栈顶的索引 如果这个索引比原来的栈顶大那么空余的部分将被赋值为nil 反之则移除多余数据 LUA_API void (lua_settop) (lua_State *L, int idx); //将索引处的数据复制之后插入栈顶 LUA_API void (lua_pushvalue) (lua_State *L, int idx); LUA_API void (lua_rotate) (lua_State *L, int idx, int n); //将fromidx处得数据复制到toidx LUA_API void (lua_copy) (lua_State *L, int fromidx, int toidx); //将栈的可用空间扩大至n 如果栈已经足够大也不会缩小栈的大小 如果失败返回0 //将对应类型的数据压数栈顶 LUA_API int (lua_checkstack) (lua_State *L, int n); LUA_API void (lua_pushnil) (lua_State *L); LUA_API void (lua_pushnumber) (lua_State *L, lua_Number n); LUA_API void (lua_pushinteger) (lua_State *L, lua_Integer n); LUA_API const char *(lua_pushlstring) (lua_State *L, const char *s, size_t len); LUA_API const char *(lua_pushstring) (lua_State *L, const char *s); LUA_API const char *(lua_pushvfstring) (lua_State *L, const char *fmt, va_list argp); LUA_API const char *(lua_pushfstring) (lua_State *L, const char *fmt, ...); LUA_API void (lua_pushcclosure) (lua_State *L, lua_CFunction fn, int n); LUA_API void (lua_pushboolean) (lua_State *L, int b); LUA_API void (lua_pushlightuserdata) (lua_State *L, void *p); LUA_API int (lua_pushthread) (lua_State *L); //将索引出数据类型按照double类型返回 isnum返回是都转换成功 LUA_API lua_Number (lua_tonumberx) (lua_State *L, int idx, int *isnum); //将索引出数据类型按照int类型返回 isnum返回是都转换成功 LUA_API lua_Integer (lua_tointegerx) (lua_State *L, int idx, int *isnum); //其他的转换函数 LUA_API int (lua_toboolean) (lua_State *L, int idx); LUA_API const char *(lua_tolstring) (lua_State *L, int idx, size_t *len); LUA_API lua_Unsigned (lua_rawlen) (lua_State *L, int idx); LUA_API lua_CFunction (lua_tocfunction) (lua_State *L, int idx); LUA_API void *(lua_touserdata) (lua_State *L, int idx); LUA_API lua_State *(lua_tothread) (lua_State *L, int idx); LUA_API const void *(lua_topointer) (lua_State *L, int idx);
正索引和负索引
从上面的luac提供的api可以看出,无论你要对栈做什么操作,都需要一个索引确定数据的位置。那么栈的索引是如何对应的呢?
lua的虚拟栈索引分为两种,一种是正索引,从1开始然后2、3、4、5.....依次变大,1的位置是栈底,然后依次往上。一种是负索引,从-1开始然后-2、-3、-4、-5.....依次变小,-1的的位置是栈顶,然后依次往下。做这样的区分是为了能够在不知道栈的大小时,依然能让开发者方便的定位到栈顶和栈底。
写个代码测试一下
//初始化lua虚拟机 lua_State * L= nullptr; L = luaL_newstate(); //载入lua基本库 luaL_openlibs(L); //依次压入a,b,c lua_pushstring(L,"a"); lua_pushstring(L,"b"); lua_pushstring(L,"c"); //依次输出 std::cerr << "---------------------" << std::endl; std::cerr << lua_tostring(L,1) << std::endl; std::cerr << lua_tostring(L,2) << std::endl; std::cerr << lua_tostring(L,3) << std::endl; std::cerr << "---------------------" << std::endl; std::cerr << lua_tostring(L,-1) << std::endl; std::cerr << lua_tostring(L,-2) << std::endl; std::cerr << lua_tostring(L,-3) << std::endl;
按照正负索引的解释,那么栈内的数据应该是这样的
控制台输出:
a
b
c
---------------------
c
b
a
看来确实如此
获取一个全局变量,lua虚拟机做了哪些事情
在luac中获取lua脚本中的一个全局变量时,首先调用lua_getglobal(),然后调用lua_toxxxx()就可以获取到变量的值(参考:c++与lua的互相调用)。那么这个两个函数到底做了些什么事情呢?
首先来看一下这一张图
- 将变量名压入栈顶,并通知lua虚拟机获取变量的值
- lua虚拟机从栈顶获取变量名,并弹出变量名
- lua虚拟机通过变量名在lua的全局变量表中查找
- 找到值之后将值压入栈顶
- 调用lua_toxxx()函数将栈顶的值按照对应类型取出来(准确的说是给到栈内对应类型的指针)
由此可以看出,似乎这个虚拟栈做了luac与lua虚拟机交互的中介。不过确实也是如此,与lua虚拟机的交互其实就是一个不停的将数据压入栈取出栈的过程。所以我们在编写代码的时候,要格外的留心数据入栈出栈的顺序,这样才能保证你每一步的操作都是正确的。
栈的刷新机制
未完待续
文章评论