Lua的调用原理(Lua的堆栈)


Lua栈

什么是Lua栈

  Lua的栈类似于以下定义,它是在创建lua_State的时候被创建的:

TValue stack[max_stack_len]//欲知内情可以查看lstate.c文件中的stack_init函数。

  存入栈的数据结构包含数值、字符串、指针、table、闭包等。

通过Lua栈实现和C/C++的通讯

Lua和C通信的约定

“如果你想要什么,你告诉我,我来产生“就可以保证,凡是Lua中的变量,Lua要负责这些变量的生命周期和垃圾回收,所以,必须由Lua来创建这些值(在创建时就加入了生命周期对其进行管理)。

  ”然后放到栈上,你只能通过API来操作这个值“,Lua的API给C提供了一套完备的操作界面,这个就相当于约定的通信协议,如果Lua客户使用这个操作界面,那么Lua本身不会出现任何”意料之外“的错误。

  ”我只管我的世界“这句话体现了Lua和C/C++作为两个不同系统的分界,C/C++中的值,Lua是不知道的,Lua只负责它的世界。

栈的索引规则

  栈底到栈顶索引是+1递增的规律,同时索引有正数索引和负数索引两种表达方式:

  1. 正数索引,不需要知道栈的大小,我们就能知道栈底在哪,栈底的索引永远是1。
    即:栈底是1,然后一直到栈顶逐渐+1
  2. 负数索引,不需要知道栈的大小,我们就能知道栈顶在哪,栈顶的索引永远是-1。
    即:栈顶是-1,然后一直到栈底逐渐-1

Lua和C++通讯实例

实际上Lua本质是和C通讯的,C++支持extern “C”。

  假设在一个Lua文件中有如下定义:

-- hello.lua文件
myName = "beauty girl"

  想要在C++中获取到myName的值,可以使用lua_getglobal来获取:

/*获取table变量,在栈顶*/
lua_getglobal(pL,"myName");

  lua_getglobal的处理过程如下:(请注意红色数字,代表通信顺序)

lua运行速度 lua运行原理_lua运行速度

  1. C++想要获取Lua的myName字符串的值,所以它把myName放到Lua堆栈(栈顶),以便Lua能看到。
  2. Lua从堆栈(栈顶)中获取myName,此时栈顶再次变为空。
  3. Lua拿着这个myName去Lua全局变量查找myName对应的字符串。
  4. Lua把取得的“beauty girl”字符串放到堆栈(栈顶)。
  5. C++可以从Lua堆栈中取得“beauty girl”。

  现在,我们给hello.lua文件中添加一个table全局变量:

-- hello.lua文件
myName = "beauty girl"
helloTable = {name = "mutou",IQ = 125}

  我们看到,多了一个helloTable的变量,他和数组十分相似,又和HashMap有点类似,总之它很强大。

  获取helloTable变量的方式和以前是一样的:

/* 取得table变量,在栈顶 */  
lua_getglobal(pL, "helloTable");

  这样,helloTable变量就被存放到栈顶。

  可我们并不是要取table变量,因为C++中是无法识别Lua的table类型的,所以我们要取得table中具体的值,也就是name和IQ的值。

  有一个和lua_getglobal类似的函数,叫做lua_gettable,顾名思义,它是用来取得table相关的数据的。

lua_gettable函数调用后,Lua会从栈顶(注意是栈顶,即如果想同时push进入两个key值,那么根据栈的原理,先进后出,那么后push进入的key值对应的value值先取,替换的值永远放在栈顶!)取得一个key值,然后根据这个key值去栈中指定的table中寻找对应的value值,最后把找到的value值放到栈顶,同时栈中key值将被移除。

  lua_pushstring()函数可以把C++中的字符串存放到Lua的栈里,然后再用lua_gettable()去执行前面所说的步骤,lua_gettable的第二个参数是指定的table变量在栈中的索引。

  为了方便理解,我们画个图来表示:

lua运行速度 lua运行原理_lua调用原理_02

  这是初始状态,堆栈里还没有任何东西,那么,现在要先把helloTable变量放到栈顶:

/* 取得table变量,在栈顶 */  
lua_getglobal(pL, "helloTable");

  然后就变成了这样:

lua运行速度 lua运行原理_Lua堆栈_03

  接着,我们要取得table的name对应的值,那么,先要做的是把“name”字符串入栈:

/*将C++的字符串放到Lua的栈中,此时,栈顶变为“name”,helloTable的对象变为栈底*/
lua_pushstring(pL,"name");

  然后变成这样:

lua运行速度 lua运行原理_lua运行速度_04

  注意,这里加上相应的索引,采用负数索引。

  由于“name”的入栈,现在helloTable变量已经不在栈顶。接着,我们调用要做的最重要的一步,取得name在table中对应的值。

/*从table对象寻找“name”对应的值(table对象现在在索引为-2的栈中,也就是当前的栈底),取得对应值之后,取代"name"返回栈顶。*/
lua_gettable(pL,-2);

  此时,栈变成这样:

lua运行速度 lua运行原理_Lua堆栈_05

完整实例:

int main (int argc, char **argv) {
    // 创建一个lua虚拟机
    lua_State* L = luaL_newstate();

    // 加载一个常用的系统库
    luaL_openlibs(L);

    // 加载lua文件并执行
    if (luaL_dofile(L, "main.lua")) {
        printf("%s\n", lua_tostring(L, -1));
    }

    // 取得table变量,在栈顶
    lua_getglobal(L, "helloTable");
    int top = lua_gettop(L);
    printf("%d\n", top);

    // 将C字符串放到Lua堆栈中,此时栈顶为"name",tbTable对象在栈底
    lua_pushstring(L, "name");
    top = lua_gettop(L);
    printf("%d\n", top);

    // 从tbTable对象中寻找"name"对应的值(tbTable对象现在在负数索引为-2【正数索引为1】
    // 的栈中,也就是栈底),取得对应的值后,用value值取代key值返回栈顶!!
    lua_gettable(L, -2);
    top = lua_gettop(L);
    printf("%d\n", top);

    const char* value = lua_tostring(L, -1);
    printf("%s\n", value);

    top = lua_gettop(L);
    printf("%d\n", top);
    if (LUA_TTABLE == lua_type(L, -2)) {
        printf("table\n");
    }

    if (LUA_TSTRING == lua_type(L, -1)) {
        printf("string\n");
    }

    // 关闭lua虚拟机
    lua_close(L);
    return 0;
}

输出结果:

lua运行速度 lua运行原理_lua调用原理_06

  lua_gettable到底做了什么事情?

  首先,我们来解释一下lua_gettable的第二个参数,-2是什么意思,-2就是刚刚helloTable变量在栈中的索引。然后,Lua会去取得栈顶的值(之前的栈顶是“name”),然后拿着这个值去helloTable变量中寻找对应的值,当然就找到“mutou”了。

  最后,Lua会把找到的值入栈,于是“mutou”就到了栈顶。