lua 函数调用1 -- 闭包详解和C调用

这里,简单的记录一下lua中闭包的知识和C闭包调用

前提知识: 在lua api小记2中已经分析了lua中值的结构,是一个 TValue{value,tt}组合,如果有疑问,可以去看一下

 

一些重要的数据结构

 

    lua中有两种闭包,c闭包和lua闭包

    两种闭包的公共部分:

       #define ClosureHeader CommonHeader;     lu_byte isC;        lua_byte nupvalues; GCObject* gclist;       struct Table env

                                                                  /*是否是C闭包*/     /*upval的个数*/                                   /* 闭包的env,set/getenv就是操纵的它 */

 

    C闭包的结构

        struct CClosure{

             ClosureHeader;

             lua_CFunction f;

             TValue upvalue[1];

        }

      结构比较简单,f是一个满足 int lua_func(lua_State*) 类型的c函数

      upvalue是创建C闭包时压入的upvalue,类型是TValue,可以得知,upvalue可以是任意的lua类型

 

    Lua闭包结构

       struct LClosure{

           ClosureHeader;

           strcut Proto* p;

           UpVal* upvals[1];

       }

      Proto的结构比较复杂,这里先不做分析

 

   统一的闭包结构,一个联合体,说明一个闭包要么是C闭包,要么是lua闭包,这个是用isC表识出来的.

       union Closure{

            CClosure c;

            LClosure  l;

       }

 

纠结的闭包

       为什么大家叫闭包,不叫它函数,它看起来就是函数啊? 为什么要发明一个"闭包"这么一个听起来蛋疼的词呢? 我也纠结在这里好久了,大概快一年半了吧~~~=.=我比较笨~~~随着看源码,现在想通了,拿出一些的自己在研究过程中的心得[尽量的通俗易懂]:

       1. c 语言中的函数的定义: 对功能的抽象块,这个大家没什么异议吧.

       2. lua对函数做了扩展:

              a. 可以把几个值和函数绑定在一起,这些值被称为upvalue.

              ps:  可能有人觉得c++的函数对象也可以把几个值和函数绑定起来啊,是这样的,但是这个问题就像是"在汇编中也可以实现面向对象呀"一样,lua从语言层面对upvalue提供了支持,就像c++/java从语言层面提供了对类,对象的支持一样,当然大大的解放了我们程序员的工作量,而且配上lua动态类型,更是让人轻松了不少.

              b. 每个函数可以和一个env(环境)绑定.

              ps:  如果说上面的upvalue还能在c++中coding出来,那么env 上下文环境这种动态语言中特有的东西c++就没有明显的对应结构了吧? 可能有人觉得lua是c写的,通过coding也可以实现,好吧=.=,"能做和做"是两码事,就想你能步行从北京到上海,不表明你就必须要这么做. env是非常重要和有用的东西,它可以轻松创造出一个受限的环境,就是传说中的"沙盒",我说的更通俗一点就是"一个动态名字空间机制". 这个先暂时不分析.

 

       好了,现在我们看到

            c       函数    { 功能抽象 }

            lua    闭包     {功能抽象,upvalue,env}

            重点: 闭包 == {功能抽象,env}

  

       看到这里,大家都明白了,如果把lua中的{功能抽象,env}也称为函数,不但容易引起大家的误解以为它就是和c函数一样,而且它确实不能很好的表达出lua函数的丰富内涵,闭包,"闭" 是指的它是一个object,一个看得见摸得着的东西,不可分割的整体(first class); "包" 指的是它包含了功能抽象,env. 这里一个很有趣的事实就是,{功能抽象,env}是很多动态语言的一个实现特征,比如lua,javascript都有实现这样的结构,它是先被实现出来,然后冠以"闭包"这样一个名称. 所以,你单单想去理解闭包这个词的话,基本是没有办法理解的,去网上查闭包,没用,你能查到的就是几个用闭包举出的例子,看完以后保证你的感觉是"这玩意挺神秘的,但是还是不懂什么是闭包",为什么不懂?  因为它指的是一种实现结构特征,是为了实现动态语言中的函数first class和上下文概念而创造出来的.

       宁可多说几句,只要对加深理解有好处就行,有这样两个个句子"我骑车去买点水果" "我用来闭包{功能抽象,env}实现动态语言中的函数first class和上下文概念",闭包和"骑车"都是你达到目地的一种手段,为了买水果你才想了"骑车"这样一个主意,并不是为了骑车而去买水果. 只把把眼睛盯在骑车上是不对的,它只是手段.

 

 

向lua中注册c函数的过程是通过lua_pushcclosure(L,f,n)函数实现的

 

       流程:  1. 创建一个 sizeof(CClosure) + (n - 1) * sizeof(TValue)大小的内存,这段内存是 CClosure + TValue[n],并做gc簿记[这点太重要了,为什么lua要控制自己世界中的所有变量,就是因为它要做gc簿记来管理内存],  isC= 1 标示其是一个C闭包.

             2. c->f = f绑定c函数.    ---------  闭包.功能抽象 = f

             3. env = 当前闭包的env[这说明了被创建的闭包继承了创建它的闭包的环境].  ----------- 闭包.env = env

             4. 把栈上的n个元素赋值到c->upvalue[]数组中,顺序是越先入栈的值放在upvalue数组的越开始位置,c->nupvalues指定改闭包upvalue的个数.  ---------- 闭包.upvalue = upvalue

             5. 弹出栈上n个元素,并压入新建的Closure到栈顶.

       整个流程是比较简单的,分配内存,填写属性,链入gc监控,绑定c函数,绑定upvalue,绑定env一个C闭包就ok了,请结合上面给的闭包的解释,很清楚了.

 

现在来解析这个C闭包被调用的过程[注意,这里只涉及C闭包的调用]

 

       lua 闭包调用信息结构:

            struct CallInfo {
                 StkId base;  /* base for this function */     ---- 闭包调用的栈基
                 StkId func;  /* function index in the stack */  ---- 要调用的闭包在栈上的位置
                 StkId    top;  /* top for this function */     ---- 闭包的栈使用限制,就是lua_push*的时候得看着点,push太多就超了,可以lua_checkstack来扩
                 const Instruction *savedpc;      ---- 如果在本闭包中再次调用别的闭包,那么该值就保存下一条指令以便在返回时继续执行
                 int nresults;  /* expected number of results from this function */   ---- 闭包要返回的值个数
                 int tailcalls;  /* number of tail calls lost under this entry */   ---- 尾递归用,暂时不管
            }

        从注释就可以看出来,这个结构是比较简单的,它的作用就是维护一个函数调用的有关信息,其实和c函数调用的栈帧是一样的,重要的信息base –> ebp,func –> 要调用的函数的栈index,savedpc –> eip,top,nresults和tailcalls没有明显的对应.

        在lua初始化的时候,分配了一个CallInfo数组,并用L->base_ci指向该数组第一个元素,用L->end_ci指向该数组最后一个指针,用L->size_ci记录数组当前的大小,L->ci记录的是当前被调用的闭包的调用信息.

 

        下面讲解一个c闭包的调用的过程:

        情景: c 函数 int lua_test(lua_State* L){

                          int a = lua_tonumber(L,1);

                          int b = lua_tonumber(L,2);

                          a = a + b;

                          lua_pushnumber(L,a);

                 }

                 已经注册到了lua 中,形成了一个C闭包,起名为"test",下面去调用它

                 luaL_dostring(L,"c = test(3,4)")

 

         1. 首先,我们把它翻译成对应的c api

            

lua3

                                1. 最初的堆栈

 

               lua_getglobal(L,“test”)

               lua_pushnumber(L,3)

               lua_pushnumber(L,4)      

            

lua2

                              2. 压入了函数和参数的堆栈

 

               lua_call(L,2,1)

            

lua5

                               3. 调用lua_test开始时的堆栈

 

            

lua4

                               4. 调用结束的堆栈

 

               lua_setglobal(L,“c”)     

            

lua3

                               5. 取出调用结果的堆栈

 

        我们重点想要知道的是lua_call函数的过程

            1. lua的一致性在这里再一次的让人震撼,不管是dostring,还是dofile,都会形成一个闭包,也就是说,闭包是lua中用来组织结构的基本构件,这个特点使得lua中的结构具有一致性,是一种简明而强大的概念.

            2. 根据1, a = test(3,4)其实是被组织成为一个闭包放在lua栈顶[方便期间,给这个lua闭包起名为bb],也就说dostring真正调用的是bb闭包,然后bb闭包执行时才调用的是test

 

         [保存当前信息到当前函数的CallInfo中]

            3. 在调用test的时刻,L->ci记载着bb闭包的调用信息,所以,先把下一个要执行的指令放在L->ci->savedpc中,以供从test返回后继续执行.

            4. 取栈上的test C闭包 cl,用 cl->isC == 1断定它的确是一个C闭包

 

         [进入一个新的CallInfo,布置堆栈]

            5. 从L中新分配一个CallInfo ci来记录test的调用信息,并把它的值设置到L->ci,这表明一个新的函数调用开始了,这里还要指定test在栈中的位置,L->base = ci->base = ci->func+1,注意,这几个赋值很重要,导致的堆栈状态由图2转化到图3,从图中可以看出,L->base指向了第一个参数,ci->base也指向了第一个参数,所以在test中,我们调用lua_gettop函数返回的值就是2, 因为在调用它的时候,它的栈帧上只有2个元素,实现了lua向c语言中传参数.

 

        [调用实际的函数]

            6. 安排好堆栈,下面就是根据L->ci->func指向的栈上的闭包(及test的C闭包),找到对应的cl->c->f,并调用,就进入了c函数lua_test

 

        [获取返回值调整堆栈,返回原来的CallInfo]

            7. 根据lua_test的返回值,把test闭包和参数弹出栈,并把返回值压入并调整L->top

            8. 恢复 L->base,L->ci 和 L->savedpc,继续执行.

        总结: 调用一个新的闭包时 1. 保存当前信息到当前函数的CallInfo中 2. 进入一个新的CallInfo,布置堆栈  3. 调用实际的函数  4. 获取返回值调整堆栈,返回原来的CallInfo

 

 

1. 创建lua虚拟机

lua_State *lua_newstate (lua_Alloc f,void *ud)

创建一个新的独立的lua虚拟机. 参数指定了内存分配策略及其参数,让用户可以定制内存分配策略是十分有用的,比如在游戏服务器端使用lua,我做过一次统记lua在运行的时候会大量的分配大小小于128字节的内存块,在这样的环境下,使用lua原生的分配器就不太适合了,还好在服务器端,我们往往已经实现了memory pool,这时只需要写一个符合 lua_Alloc 原型的适配器,然后指定为lua的内存分配器就可以了,很灵活.

从lua的设计层面来说,lua只是内存分配器的用户,它只使用一个简单的接口来分配内存,而不去实现如何分配,毕竟内存分配不在lua的功能范围内,这样使的lua变的更加紧凑,它只是专注于实现lua本身,而不需要去关注内存分配策略这样的和lua本身无关的东西. 其实学习lua源代码不光是为了更好的掌握lua,也是为了学习lua中的体现出来的一些编程思想,lua是一个高度的一致性的,优雅的软件作品

失败返回null,多是因为内存分配失败了

该函数会创建栈

从该函数学习到的东西:  1. 当你制作一个功能时,最好是理清该功能的核心概念和需求,然后去实现他们,功能要模块化,核心概念之间应该是概念一致的,联系紧密的[谈何容易,只能是尽可能的,随时提醒自己要有这样的想法].

                                2. 不要因为功能的实现问题而将一个非该功能核心概念的东西加进来,反之应该把这些东西抽象化作为用户可配置的形式.[在实现时很容易发生"要用到某个功能了,就是实现它"这样的情况,这样并不好]就比如lua,它的核心概念就是lua虚拟机,而内存分配只是在实现lua虚拟机的过程中的要用到的一种东西,但它本身不在lua的核心概念里面,所以把它暴露出来,让用户自己去定制.

                                再说下去就是: 除了系统最核心的功能,其他的东西能用插件的形式暴露给用户,使其可配置可扩展.

 

关于这个函数,还要做更多的解释,比如我们看到的lua的绝大多数api的第一个参数都是lua_State* L,而这个L就是lua_newstate制造出来的,那么在分析源码的时候,当然要去看看lua_newstate到底是干了些什么,lua_State的结构又是什么,要了解这些内容,需要知道lua的内部组织结构,下面是一张很概括但能反映其结构的图

 

JSZUY$VR25$M{O}OQV59XYW

 

可以看出来,在一个独立的lua虚拟机里,global_State是一个全局的结构,而lua_State可以有多个

值得说明的是,当调用lua_newstate的时候,主要的工作就是1. 创建和初始化global_State 2. 创建一个lua_State,下面来详细的讲解global_State的内容和作用.

 

global_State

一个lua虚拟机中只有一个,它管理着lua中全局唯一的信息,主要是以下功能

1. 内存分配策略及其参数,在调用lua_newstate的时候配置它们. 也可以通过lua_getallocf和lua_setallocf随时获取和修改它

2. 字符串的hashtable,lua中所有的字符串都会在该hashtable中注册.

3. gc相关的信息. 内存使用统计量.

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上.

global_State主要是管理lua虚拟机的全局环境.

 

lua_State

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的通信.

lua_State主要是管理一个lua虚拟机的执行环境,一个lua虚拟机可以有多个执行环境.

 

lua_newstate函数的流程

经过上面的分析,可以看出newstate = [new 一个 global_state] + [new 一个 lua_State],现在看一下它的流程,很简单

1. 新建一个global_state和一个lua_State.

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

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

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

 

 

2. 创建新lua执行环境

lua_State *luaE_newthread (lua_State *L)

创建一个新的lua_State,预分配CallInfo和stack空间,并共享l_gt表,虽然每个lua_State都有自己的l_gt,但是这里是却将新建的lua_State的l_gt都指向主lua_State的l_gt.

注意,lua_State是lua运行的基础[CallInfo]和与c通信的基础[stack],在新的lua_State上操作不会影响到原来的lua_State:),这个是协程实现的基础. 这里顺便提一下协程,这里先引一段lua创始人的话:" 我们不信任基于抢占式内存共享的多线程技术. 在 HOPL 论文中,我们写道: "我们仍然认为,如果在连 a=a+1 都没有确定结果的语言中,无人可以写出正确的程序." 我们可以通过去掉抢占式这一点,或是不共享内存,就可以回避这个问题."协程的基础就是"去掉抢占式,但共享内存",这里的共享是在lua虚拟机的层面上的,而不是通常意义上的share memory,这里的共享内存直接就指的是不同线程[lua_State]之间,共享lua_State.l_gt全局表,全局表可以作为不同协程之间的通信环境,当然也可以用lua_xmove函数,协程的事先说到这里.

 

一个和多lua_State相关的函数是: 在同一个lua虚拟机里传递不同lua_State的值

void lua_xmove (lua_State *from,lua_State *to,int n)
把from栈上的前n个值弹出,并压入到to栈中.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.github代码实践源代码是lua脚本语言,下载th之后运行thmain.lua-netTypevgg-savevgg_cifar10/-S0.0001,报错: 试看看安装lua:报错了,参考这篇文章:ubuntu18.04安装lua的步骤以及出现的问题_weixin_41355132的博客-CSDN博客问题解决,安装成功:情况并没有好转,出现相
此文为搬运帖,原帖地址https://www.cnblogs.com/zwywilliam/p/5999924.html前言在看了uwa之前发布的《Unity项目常见Lua解决方案性能比较》,决定动手写一篇关于lua+unity方案的性能优化文。整合lua是目前最强大的unity热更新方案,毕竟这是唯一可以支持ios热更新的办法。然而作
Rime输入法通过定义lua文件,可以实现获取当前时间日期的功能。1.TIMERime是一款可以高度自定义的输入法,相关教程可以查看往期文章,关于时间获取是指输入一个指定关键字,输出当前时间,效果如下(我定义了time关键字):实现如下:①在用户文件夹中新建一个rime.lua文件加入如下代码 ti
localfunctiongenerate_action(params)localscale_action=cc.ScaleTo:create(params.time,params.scale_x,params.scale_y)localfade_action=cc.FadeIn:create(params.time)returncc.Spawn:create(scale_action,fade_action)end
2022年1月11日13:57:45 官方:https://opm.openresty.org/官方文档:https://opm.openresty.org/docs#table-of-contents为什么建议使用opm不建议使用luarocks?http://openresty.org/cn/using-luarocks.html官方解释:请注意!LuaRocks并不是OpenResty官方推荐的装包方式。LuaRoc
在Lua中的table(表),就像c#中的HashMap(哈希表),key和value一一对应。元表:table的一个操作的拓展,里面包含关联了对应的方法,元方法就是其中一个。元方法:当你通过键来访问table的时候,如果这个键没有值,那么Lua就会寻找该table的metatable(假定有metatable)中的__index键。如果__inde
表排序:table.sort(list[,comp])参数list:指定表,可选参数comp:排序函数,无参数时通常按升序排序。排序函数针对表中连续的序列,其间不可以存在空洞或nil,排序函数需要两个形参(对应表中每次参加比较的两个数据),需要一个比较两个形参表达式的返回值,不能含有等于关系,例如>=,<=,==。do
一、安装lua环境1.1安装依赖包[root@centos7~]#yuminstallgccreadline-devel1.2下线lua源码包并解压[root@centos7~]#wgethttp://www.lua.org/ftp/lua-5.3.5.tar.gz[root@centos7~]#tarxvflua-5.3.5.tar.gz-C/usr/local/src1.3进行编译[root@centos7~]
官网OpenResty® 是一个基于 Nginx 与Lua的高性能Web平台,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态Web应用、Web服务和动态网关。OpenResty® 通过汇聚各种设计精良的 Nginx 模块(主要由
表参考《lua程序设计》可以认为,表是一种动态分配的对象,程序只能操作指向表的引用(或指针)。除此以外,Lua语言不会进行隐藏的拷贝(hiddencopies)或创建新的表--创建表a={}--创建空表k="x"a[k]=10--键“x”值10a[20]="great"--键20值“great”print(a["x"])-->10
https://github.com/galenho/crossover.git一个跨平台的lua游戏服务器开发框架,该框架采用多线程并发来处理消息,开发者只需要调用相应的接口函数并绑定相应的回调函数即可,在逻辑层表现为单线程的开发模式,使开发者易用,易调试,易维护,易扩展,同时拥有快速的响应能力。   框架使用面
参考链接:https://www.runoob.com/lua/lua-metatables.htmlhttps://www.jianshu.com/p/cb945e7073a3 元表是一个table,可以让我们改变table的行为,每个行为有对应的元方法例如,对table进行设置键值,查找键值,运算等,就会触发对应的元方法1--__index:table被访问时,如果找不到这
https://github.com/yuin/gopher-luahttps://github.com/yuin/gopher-lua Lua5.1ReferenceManual-contentshttp://www.lua.org/manual/5.1/ go中使用luapackagemainimport( lua"github.com/yuin/gopher-lua")funcmain(){ l:=lua.NewState() d
编译问题不要留到运行时才跑出来啊。早上9:00-中午3:00,6个小时,服了自己了。 写了一个测试,springboot+redis+lua执行到redisTemplate.execute(redisScript,idList)的时候一直报错,integer无法转换为string。我一直以为是lua脚本写错了,翻文档翻过来又翻过去,写法变了又变,还是解
        。。是字符串连接符,字典用=号连接,  注意fordoend都是连一起,  注意ifthen,  如果local在函数里,是可以访问,非local出了函数一样能用,  doend代码块也是一样,    注意点号表示,只能key是字符串,  注意括号不是必须
C语言与Lua之间的相互调用详解写一个C调用Lua的Demo编译运行C语言调用Lua编译问题总结正确的编译命令问题1:缺少-lm参数问题2:缺少-ldl参数​1、为什么会出现undefinedreferenceto‘xxxxx’错误?​2、-l参数和-L参数写一个C调用Lua的Demo编译运行add.c内容//你需要
1、动态输出打开E:\study\openresty\openresty-1.19.9.1-win64目录下的confginx.conf文件在server中增加一下代码 location/hello{ default_typetext/html; content_by_lua'ngx.say("<p>hello,world</p>")'; }运行后,效果如下图localhost
参见:lipp/lua-websockets:WebsocketsforLua.(github.com)github网址可能需手动转换lipp.github.com/lua-websockets/>github.com/lipp/lua-websocketswebsockets为底层的类似于TCP、UDP的socket(实现上基于更底层的socket),不同于上层的webserver服务端(Service)需并行地支持多
lua发送消息到rabbitmq,我们选择类库lua-resty-rabbitmqstomp 来完成这个任务。类库安装:进入nginx.conf中 lua_package_path 中对应的目录下的resty目录(没有则创建),执行:wget-chttps:/aw.githubusercontent.com/wingify/lua-resty-rabbitmqstomp/master/libes
1Lua介绍Lua是一门以其性能著称的脚本语言,被广泛应用在很多方面。Lua一般用于嵌入式应用,现在越来越多应用于游戏当中,魔兽世界,愤怒的小鸟都有用到。优势Lua极易嵌入到其他程序,可当做一种配置语言。提升应用性能,比如:游戏脚本,nginx,wireshark的脚本兼容性强,可以直接使用C