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

这里, 简单的记录一下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不久, 而且表达能力有限, 大家如果要看的话取其精华, 舍弃糟粕吧:)

Lua中闭包详解 来自RingOfTheC[ring.of.the.c@gmail.com]的更多相关文章

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

    转自:http://www.cnblogs.com/ringofthec/archive/2010/11/05/luaClosure.html 这里, 简单的记录一下lua中闭包的知识和C闭包调用 前 ...

  2. Lua的闭包详解(终于搞懂了)

    词法定界:当一个函数内嵌套另一个函数的时候,内函数可以访问外部函数的局部变量,这种特征叫做词法定界 table.sort(names,functin (n1,n2) return grades[n1] ...

  3. python中闭包详解

    谈谈自己的理解:python中闭包,闭包的实质   闭包这个概念好难理解,身边朋友们好多都稀里糊涂的,稀里糊涂的林老冷希望写下这篇文章能够对稀里糊涂的伙伴们有一些帮助~ 请大家跟我理解一下,如果在一个 ...

  4. Swift 中的Closures(闭包)详解

    Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中, ...

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

    这里, 简单的记录一下lua中闭包的知识和C闭包调用 前提知识: 在lua api小记2中已经分析了lua中值的结构, 是一个 TValue{value, tt}组合, 如果有疑问, 可以去看一下 一 ...

  6. Python闭包详解

    Python闭包详解 1 快速预览 以下是一段简单的闭包代码示例: def foo(): m=3 n=5 def bar(): a=4 return m+n+a return bar >> ...

  7. winxp计算机管理中服务详解

    winxp计算机管理中服务详解01 http://blog.sina.com.cn/s/blog_60f923b50100efy9.html http://blog.sina.com.cn/s/blo ...

  8. cocos2dx常见的46中+22中动作详解

    cocos2dx常见的46中+22中动作详解 分类: iOS2013-10-16 00:44 1429人阅读 评论(0) 收藏 举报 bool HelloWorld::init(){    ///// ...

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

    转自:http://blog.csdn.net/qinjuning/article/details/7310620Android中Context详解 ---- 你所不知道的Context 大家好,  ...

随机推荐

  1. 【linux】【指令集】查看是否打开selinux

    > getenforce selinux相关原理资料参考 <鸟哥的linux私房菜>  http://cn.linux.vbird.org/linux_server/0210netw ...

  2. Manjaro 添加国内源和安装搜狗输入法

    Manjaro 系统虽然比 Ubuntu 用着稳定,但有些小地方没有 Ubuntu 人性化,比如默认安装完的系统貌似没有中国的,Ubuntu 估计是用的人多,所以安装完后会根据所在地给你配置更新的源. ...

  3. JAVA基础篇—异常

    五种常见异常 1.NullPointerException 空指针 2.ClassNotFoundException 指定类不存在 3.ArithmeticException运算异常 4.ArrayI ...

  4. iOS 中的视图函数 init initwithnib viewDidLoad viewWillAppear的总结

    我要总结的函数主要是这几个: UIView *view-如果view还没有被初始化的话,getter方法会先调用[self loadView],如果getter或者setter方法被重写了,子类中的g ...

  5. html5/css3常考面试题

    一.HTML5 CSS3 CSS3有哪些新特性? 1. CSS3实现圆角(border-radius),阴影(box-shadow), 2. 对文字加特效(text-shadow.),线性渐变(gra ...

  6. html--元素显示优先级

    HTML元素的显示优先级 一.HTML元素的显示优先级(显示层次问题,哪个在上哪个在下!总是显示在最前面)        帧元素>HTML元素优先,表单元素总>非表单元素优先        ...

  7. Django Model two

    Django_model: eg: class XXXX(models.Model): nid = models.AutoField(primary_Key=True) name = models.C ...

  8. loj2030 「SDOI2016」储能表

    ref ref 一个点就是一个数对 \((x,y)\). 记状态 \(f[i][1/0][1/0][1/0]\) 和 \(g[i][1/0][1/0][1/0]\),其中三个 \(1/0\) 取值分别 ...

  9. Selenium WebDriver-actionchain模拟键盘操作

    #encoding=utf-8 import unittest import time import chardet from selenium import webdriver from selen ...

  10. Selenium WebDriver-通过键盘事件操作浏览器

    #encoding=utf-8 import unittest import time import chardet from selenium import webdriver class Visi ...