zoukankan      html  css  js  c++  java
  • lua堆栈

    lua堆栈

    来源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/56702381

    来源 https://blog.csdn.net/suhuaiqiang_janlay/article/details/63683036

    一、Lua脚本语言

    1. 概述

    Lua是一种脚本编程语言,与一般脚本语言不同,被称为是嵌入式的脚本语言。Lua最著名的应用是在暴雪公司的网络游戏魔兽世界中。

    Lua语言可以独立进行编程,但这不是其主要的使用方式。Lua最典型的用法,是作为一个库,嵌入到其他大型语言(称为宿主语言)的应用程序之中,为应用程序提供参数配置或逻辑描述等功能,带来前所未有的灵活性。

     

    Lua常见的宿主语言有:C/C++、Java、.NET,甚至脚本语言如PHP、Ruby。


    2. Lua与相似解决方案的比较

     

    Lua体积很小,往往使用静态链接嵌入到程序内部,在发布应用时不需要附带任何的运行时支持。

    3. 宿主语言中嵌入Lua的工作流程

    (1)宿主语言建立Lua解释器对象

    (2)将宿主语言实现的Lua扩展,如函数等,注册到Lua解释器中,供其使用。

    (3)读入Lua源程序或预先编译好的Lua程序。

    (4)执行读入的Lua程序。

    二、Lua虚拟机的初始化

    Lua工作的核心是Lua虚拟机,宿主语言在加载和执行Lua脚本时,做的第一件事情就是创建并初始化Lua虚拟机。


    1. 创建Lua虚拟机

    lua_State *lua_newstate(lua_Alloc f, void *ud) API可以为我们创建一个新的独立的Lua虚拟机。

    参数指定了虚拟机中的内存分配策略,例如我们已经在自己的代码中实现了内存池,这时候只需要写一个符合lua_Alloc原型的适配器,然后指定为Lua的内存分配器就可以了,使得内存分配更加灵活。当然,如果不想自定义内存分配策略,也可以使用luaL_newstate,这样Lua会帮你定义默认的内存分配策略。

    我们可以先来看一下luaL_newstate的源码:

    // luaconf.h
    /*
    @@ LUA_EXTRASPACE defines the size of a raw memory area associated with
    ** a Lua state with very fast access.
    ** CHANGE it if you need a different size.
    */
    #define LUA_EXTRASPACE        (sizeof(void *))
     
    // lstate.c
    /*
    ** thread state + extra space
    */
    typedef struct LX {
      lu_byte extra_[LUA_EXTRASPACE];
      lua_State l;
    } LX;
     
    /*
    ** Main thread combines a thread state and the global state
    */
    typedef struct LG {
      LX l;
      global_State g;
    } LG;
     
    // lstate.c
    LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
      int i;
      lua_State *L;
      global_State *g;
      LG *l = cast(LG *, (*f)(ud, NULL, LUA_TTHREAD, sizeof(LG)));
      if (l == NULL) return NULL;
      L = &l->l.l;
      g = &l->g;
      ......
      return L;
    }

    可见,通过luaL_newstate 创建Lua虚拟机时,第一块申请的内存将用来存储global_State(全局状态机)和lua_State(主线程)实例。为了避免内存碎片的产生,同时减少内存分配和释放的次数,Lua采用了一个小技巧:利用一个LG结构,把分配lua_State和global_State的行为关联在一起。这个LG结构是在C文件内部定义,而不存在公开的H文件中,仅供该C代码文件使用,因此这种依赖数据结构内存布局的用法负作用不大。

     

    2. 关于global_State和lua_State

    在一个独立的Lua虚拟机中,global_State是一个全局的结构, 而lua_State可以有多个。

    global_State

    global_State结构,我们可以称之为Lua全局状态机。从Lua的使用者角度来看,global_State结构是完全感知不到的:我们无法用Lua公开的API获取到它的指针、句柄或引用,而且实际上我们也并不需要引用到它。但是对于Lua的实现来说,global_State是十分重要的部分。

    它管理着Lua中全局唯一的信息,主要是以下功能:


    (1)内存分配策略及其参数

    在调用lua_newstate的时候配置它们. 也可以通过lua_getallocf和lua_setallocf随时获取和修改它

    (2)字符串的hashtable

    全局的字符串哈希表,即保存那些短字符串,使得整个虚拟机中短字符串只有一份实例。具体参见 Lua字符串处理

    (3)垃圾回收相关的信息,内存使用统计量

    (4)panic, 当无保护调用发生时, 会调用该函数, 默认是null, 可以通过lua_atpanic配置.(用于异常处理)

    (5)注册表, 注册表是一个全局唯一的table

    (6)记录lua中元方法名称 和 基本类型的元表

    [注意, lua中table和userdata每个实例可以拥有自己的独特的元表--记录在table和userdata的mt字段, 其他类型是每个类型共享一个元表--就是记录在这里].

    (7)upvalue链表

    (8)主lua_State, 一个lua虚拟机中, 可以有多个lua_State, lua_newstate会创建出一个lua_State(称为主线程), 并邦定到global_state的主lua_State上

    lua_State

    线程,这里线程的概念区别于操作系统的线程,实际上也是Lua中定义的一种状态机。lua_State主要是管理一个lua虚拟机的执行环境, 一个lua虚拟机可以有多个执行环境。


    (1)要注意的是, 和nil, string, table一样,lua_State也是lua中的一种基本类型, lua中的表示是TValue {value = lua_State, tt = LUA_TTHREAD}

    (2)lua_State的成员和功能

    a. 栈的管理, 包括管理整个栈和当前函数使用的栈的情况

    b. CallInfo的管理, 包括管理整个CallInfo数组和当前函数的CallInfo

    c. hook相关的, 包括hookmask, hookcount, hook函数等

    d. 全局表l_gt, 注意这个变量的命名, 很好的表现了它其实只是在本lua_State范围内是全局唯一的的, 和注册表不同, 注册表是lua虚拟机范围内是全局唯一的
    e. gc的一些管理和当前栈中upvalue的管理 
    f.  错误处理的支持
    (3)从lua_State的成员可以看出来, lua_State最主要的功能就是函数调用以及和c的通信.

    3. lua_newstate函数的流程

    (1)新建一个global_state和一个lua_State

    (2)初始化, 包括给g_s创建注册表, g_s中各个类型的元表的默认值全部置为0

    (3)给l_s创建全局表, 预分配l_s的CallInfo和stack空间

    (4)其中涉及到了内存分配统统使用lua_newstate传进来的内存分配器分配

    1:lua_push* 压栈API

    lua_push*这些API是把C语言里面的值封装成Lua类型的值压入栈中的,对于那些需要垃圾回收的元素,在压入栈时,都会在Lua(也就是Lua虚拟机中)生成一个副本。比如lua_pushstring(lua_State *L, const char *s)会向中栈压入由s指向的以''结尾的字符串,在C中调用这个函数后,我们可以任意释放或修改由s指向的字符串,也不会出现问题,原因就是在执行lua_pushstring过程中Lua会生成一个内部副本。实质上,Lua不会持有指向外部字符串的指针,也不会持有指向任何其他外部对象的指针(除了C函数,因为C函数总是静态的)。

    总之,一旦C中值被压入栈中,Lua就会生成相应的结构(实质就是Lua中实现的相应数据类型)并管理(比如自动垃圾回收)这个值,从此不会再依赖于原来的C值。

    2:lua栈大小

    lua 只保证在从 Lua 进入 C 的边界上提供额外的 LUA_MINSTACK 个 slot 。这个值默认为 20 ,一般是够用的。正因为一般够用,反而容易被编写 C 扩展的同学忽视。尤其是在 C 扩展的代码里有 C 层次上的递归时,非常容易在边界情况下栈溢出。因为 Lua 的 stack 实际上又经常留出超过 LUA_MINSTACK 的空间,这种 bug 不易察觉。记住:如果你在 C 扩展中做复杂的事情,一定要记得在使用 lua stack 前,用 luaL_checkstack 留够你需要的空间。


    OK,这篇文章主要是借lua_newstate讲述global_State和lua_State的结构与作用,希望对大家了解Lua工作环境有一点帮助。

    下一篇将讲述Lua栈相关的内容,更新中。。。

    参考文献:

    http://www.cnblogs.com/ringofthec/archive/2010/11/09/lua_State.html

    http://blog.csdn.net/maximuszhou/article/details/46277695

    一、Lua栈

    1. 什么是lua栈


    lua的栈类似于以下的定义, 它是在创建lua_State的时候创建的:  TValue stack[max_stack_len]  // 欲知内情可以查 lstate.c 的stack_init函数

    存入栈的数据类型包括数值, 字符串, 指针, talbe, 闭包等, 下面是一个栈的例子:

     


    2. TValue结构

    压入的类型有数值, 字符串, 表和闭包[在c中看来是不同类型的值], 但是最后都是统一用TValue这种数据结构来保存的:), 下面用图简单的说明一下这种数据结构:      

     

      p -- 可以存一个指针, 实际上是lua中的light userdata结构
      n -- 所有的数值存在这里, 不过是int , 还是float

      b -- Boolean值存在这里, 注意, lua_pushinteger不是存在这里, 而是存在n中, b只存布尔
      gc -- 其他诸如table, thread, closure, string需要内存管理垃圾回收的类型都存在这里
      gc是一个指针, 它可以指向的类型由联合体GCObject定义, 从图中可以看出, 有string, userdata, closure, table, proto, upvalue, thread


       从上面的图可以的得出如下结论:
       1. lua中, number, boolean, nil, light userdata四种类型的值是直接存在栈上元素里的, 和垃圾回收无关.

       2. lua中, string, table, closure, userdata, thread存在栈上元素里的只是指针, 他们都会在生命周期结束后被垃圾回收.

    lua value 和 c value的对应关系


                 c          lua
             nil           无    {value=0, tt = t_nil}
          boolean       int  非0, 0    {value=非0/0, tt = t_boolean}
          number       int/float等   1.5    {value=1.5, tt = t_number}
       lightuserdata    void*, int*, 各种*  point    {value=point, tt = t_lightuserdata}
          string          char  str[]    {value=gco, tt = t_string}   gco=TString obj
          table            无    {value=gco, tt = t_table}  gco=Table obj
          userdata            无    {value=gco, tt = t_udata} gco=Udata obj
          closure            无    {value=gco, tt = t_function} gco=Closure obj

    二、通过Lua栈实现和C++的通讯

    1. Lua和C通讯的约定

        lua和c通信时有这样的约定: 所有的lua中的值由lua来管理, c++中产生的值lua不知道, 类似表达了这样一种意思: "如果你(c/c++)想要什么, 你告诉我(lua), 我来产生, 然后放到栈上, 你只能通过api来操作这个值, 我只管我的世界", 这个很重要, 因为:
         "如果你想要什么, 你告诉我, 我来产生"就可以保证, 凡是lua中的变量, lua要负责这些变量的生命周期和垃圾回收, 所以, 必须由lua来创建这些值(在创建时就加入了生命周期管理要用到的簿记信息)
         "然后放到栈上, 你只能通过api来操作这个值", lua api给c提供了一套完备的操作界面, 这个就相当于约定的通信协议, 如果lua客户使用这个操作界面, 那么lua本身不会出现任何"意料之外"的错误.
         "我只管我的世界"这句话体现了lua和c/c++作为两个不同系统的分界, c/c++中的值, lua是不知道的, lua只负责它的世界。

    2. 栈的索引规则

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

    1. 正数索引,不需要知道栈的大小,我们就能知道栈底在哪,栈底的索引永远是1

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

    即:栈顶是-1,然后一直到栈底逐渐-1

    3. Lua和C++通讯实例

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

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

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

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

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

     

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

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

    -- helloLua.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函数会从栈顶取得一个值,然后根据这个值去table中寻找对应的值,最后把找到的值放到栈顶。
    lua_pushstring()函数可以把C++中的字符串存放到Lua的栈里;
    然后再用lua_gettable()取执行前面所说的步骤,lua_gettable的第二个参数是指定的table变量在栈中的索引。

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

     

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

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

    然后就变成了这样:

     

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

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

    然后变成这样:

     

    注意了,我把栈的索引也加上了,因为我们即将要使用,这次我们用负数索引。
    由于”name”的入栈,现在helloTable变量已经不在栈顶了。
    接着,我们调用要做最重要的一步了,取得name在table中对应的值:

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

    此时,栈变成这样:

     

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

    最后,简单写了个Lua和C++相互调用的实例,代码地址:Lua和C++交互示例代码

    ============= End

  • 相关阅读:
    MVC架构引入smarty视图引擎
    视图引擎smarty之插件
    视图引擎smarty 三
    视图引擎smarty 二
    视图引擎smarty 一
    .Net 框架
    onkeyup="this.value=this.value.replace(/D/g,'')
    cookie
    click
    html页面内容替换
  • 原文地址:https://www.cnblogs.com/lsgxeva/p/11158168.html
Copyright © 2011-2022 走看看