Lua中闭包详解 来自RingOfTheC[[email protected]]

这些东西是平时遇到的, 觉得有一定的价值, 所以记录下来, 以后遇到类似的问题可以查阅, 同时分享出来也能方便需要的人, 转载请注明来自RingOfTheC[[email protected]]

这里, 简单的记录一下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}

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

 

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

宁可多说几句, 只要对加深理解有好处就行, 有这样两个个句子"我骑车去买点水果" "我用来闭包{功能抽象, upvalue, 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

1. 最初的堆栈

lua_getglobal(L, “test”)

lua_pushnumber(L, 3)

lua_pushnumber(L, 4)

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

lua_call(L, 2, 1)

3. 调用lua_test开始时的堆栈

4. 调用结束的堆栈

lua_setglobal(L, “c”)

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

短短续续一共写了6个小时......... 先写到这里吧, 对了, 我是开始写blog不久, 而且表达能力有限, 大家如果要看的话取其精华, 舍弃糟粕吧:)

时间: 2024-08-02 14:51:22

Lua中闭包详解 来自RingOfTheC[[email protected]]的相关文章

python中闭包详解

闭包这个概念好难理解,身边朋友们好多都稀里糊涂的,稀里糊涂的林老冷希望写下这篇文章能够对稀里糊涂的伙伴们有一些帮助~ 请大家跟我理解一下,如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数. 闭包: 在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用.这样就构成了一个闭包. 一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失.但是闭包是一种特殊情况,如果外函数在结束的时

c/c++中const详解

c/c++中const详解 来自http://blog.csdn.net/lwbeyond/article/details/6187501 一. cons 的作用 (1) 可以定义 const 常量 const int Max=100; int Array[Max]; (2) 可以保护被修饰的东西,防止意外的修改,增强程序的健壮性 如果在函数体内修改了i,编译器就会报错: void f (const int i) { i=10;//error! } (3) 为函数重载提供了一个参考 class 

Swift 中的Closures(闭包)详解

Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中,同样有这样的一个角色,用于当开发者需要异步执行的之后使用的一种语法 - Closure.中文翻译为闭包. 闭包出了可以进行异步执行之外,它的完整使用还依赖闭包本身的变量.常量的捕获.闭包捕获并存储对它们定义的上下文中的任何常量和变量的引用,这也就意味着,你可以在任何时候异步执行闭包的时候获取之前的所

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;       struc

linux中iptables详解

linux中iptables详解 一.通用基础知识 1.基本概念 什么是防火墙? 所谓防火墙指的是工作于主机或网络的边缘,对于进出的报文根据事先定义的规则作检查,将那些能够被规则所匹配到的报文作出相应处理的组件. 防火墙是由软件和硬件设备组合而成.在内部网和外部网之间.专用网与公共网之间的界面上构造的保护屏障,使Internet与Intranet之间建立起一个安全网关(Security Gateway),从而保护内部网免受非法用户的侵入. 防火墙主要由服务访问规则.验证工具.包过滤和应用网关4个

转载:唐磊的个人博客《python中decorator详解》【转注:深入浅出清晰明了】

转载请注明来源:唐磊的个人博客<python中decorator详解> 前面写python的AOP解决方案时提到了decorator,这篇文章就详细的来整理下python的装饰器--decorator. python中的函数即objects 一步一步来,先了解下python中的函数. def shout(word='hello,world'):     return word.capitalize() + '!'print shout()#输出:Hello,world!#跟其他对象一样,你同样

postgreSQL在linux中安装详解

postgreSQL在linux中安装详解 收藏 这里安装8.4.4版本,首先下载postgresql-8.4.4-1-linux.bin.(http://www.postgresql.org/download/)下载linux32版本的1.       如果该文件不在linux系统中,将其从Windows拖到linux系统中,放到任意一个目录下,最好放在/opt或者/home.推荐一个Windows文件转到linux的工具:WinSCP3 ( 1.打开一个终端,su -成root用户:2.ch

转:iOS中socket详解

一.网络各个协议:TCP/IP.SOCKET.HTTP等 网络七层由下往上分别为物理层.数据链路层.网络层.传输层.会话层.表示层和应用层. 其中物理层.数据链路层和网络层通常被称作媒体层,是网络工程师所研究的对象: 传输层.会话层.表示层和应用层则被称作主机层,是用户所面向和关心的内容. http协议   对应于应用层 tcp协议    对应于传输层 ip协议     对应于网络层 三者本质上没有可比性.  何况HTTP协议是基于TCP连接的. TCP/IP是传输层协议,主要解决数据如何在网络

Android中Context详解 ---- 你所不知道的Context

转载至 :http://blog.csdn.net/qinjuning 前言:本文是我读<Android内核剖析>第7章 后形成的读书笔记 ,在此向欲了解Android框架的书籍推荐此书. 大家好,  今天给大家介绍下我们在应用开发中最熟悉而陌生的朋友-----Context类 ,说它熟悉,是应为我们在开发中 时刻的在与它打交道,例如:Service.BroadcastReceiver.Activity等都会利用到Context的相关方法 : 说它陌生,完全是 因为我们真正的不懂Context