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}
重点: 闭包 == {功能抽象, 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
转载于:https://www.cnblogs.com/quansir/p/3981093.html
lua 函数调用1 -- 闭包详解和C调用相关推荐
- JavaScript重难点解析4(作用域与作用域链、闭包详解)
JavaScript重难点解析4(作用域与作用域链.闭包详解) 作用域与作用域链 作用域 作用域与执行上下文 作用域链 闭包 闭包理解 将函数作为另一个函数的返回值 将函数作为实参传递给另一个函数调用 ...
- Python函数(函数定义、函数调用)用法详解
函数 函数就是一段封装好的,可以重复使用的代码,它使得我们的程序更加模块化,不需要编写大量重复的代码. 函数可以提前保存起来,并给它起一个独一无二的名字,只要知道它的名字就能使用这段代码.函数还可以接 ...
- Swift 中的Closures(闭包)详解
Swift 中的Closures(闭包)详解 在Swift没有发布之前,所有人使用OC语言编写Cocoa上的程序,而其中经常被人们讨论的其中之一 -- Block 一直备受大家的喜爱.在Swift中, ...
- JavaScript闭包详解及案例
JavaScript闭包详解及案例 一. 变量作用域 函数内部可以使用全局变量 函数外部不可以使用局部变量 当函数执行完毕时,本作用域内的局部变量会被销毁 二. 闭包 闭包:有权访问另一个函数作用域中 ...
- angular字符串转成html,详解angular如何调用HTML字符串的方法
详解angular如何调用HTML字符串的方法 前面的文章我们介绍过angular6.0的数据绑定,也就是前面页面如何调用后台的数据,我们接触到了调用普通数据--如:调用产品详情{{post.cont ...
- vue调用手机相机相册_详解Vue.js调用手机相机和相册以及上传
详解Vue.js调用手机相机和相册以及上传 发布于 2020-7-7| 复制链接 摘记: 组件 ```xhtml .. 组件 ```xhtml --> 选中{{imgList.length}}张 ...
- 详解易语言调用大漠插件注册vip的三种方法
详解易语言调用大漠插件注册vip的三种方法 1.将大漠插件所有命令封装成易语言类模块.(类模块调用法) 2.将大漠插件所有命令封装成Ec模块(EC模块调用法) 3.将大漠插件所有命令封装成易语言支持库 ...
- python闭包详解函数_详解python函数的闭包问题(内部函数与外部函数详述)
python函数的闭包问题(内嵌函数) >>> def func1(): ... print ('func1 running...') ... def func2(): ... pr ...
- redis lua 设置过期_详解 Redis 内存管理机制和实现
Redis是一个基于内存的键值数据库,其内存管理是非常重要的.本文内存管理的内容包括:过期键的懒性删除和过期删除以及内存溢出控制策略. 最大内存限制 Redis使用 maxmemory 参数限制最大可 ...
最新文章
- flux java_FluxJava 新增 RxJava2 的支援功能
- linux cpu拓扑查看工具 hwloc 简介
- 瑞典皇家理工学院工程实例:Sound localization
- Hadoop大数据--Mapreduce程序运行并发度
- 谷歌 colab_使用Google Colab在Python中将图像和遮罩拆分为多个部分
- linux复制压缩文件夹,[操作系统]linux中文件或者文件夹的基本操作(复制,移动,删除,查找,压缩)...
- linux切换至root权限,【linux】sudo su切换到root权限
- Ant Design Switch 随机生成bool开关 代码片段
- Windows下部署elasticsearch和kibana
- leetcode [26] 删除排序数组中的重复项 / Remove Duplicates from Sorted Array
- 如何在 iPhone、iPad 和 Mac 上通过 iMessage 共享照片和视频?
- 计算机程序设计语言发展经历了,简述计算机程序设计语言的发展历程
- 20155313 杨瀚 《网络对抗技术》实验二 后门原理与实践
- iphone8进入恢复模式或DFU模式
- 基础设施建设取得新进展,区块链产业化提速 | 产业区块链发展周报
- 读书笔记:《敏捷估计与规划》
- 使用 MsTimer2 库
- 基于JAVA Web的航空订票系统
- android锁屏流程_android 解锁,锁屏流程
- 苹果资讯|iOS 14.2 Beta为Control Center添加了新的Shazam音乐识别功能
热门文章
- NB-IoT,你真是太让我失望了
- 自动驾驶技术如何升级?这份技能图谱为你指路
- 美国智库报告:自动驾驶对社会、经济与劳动力的影响
- 2018人工智能前沿报告:深度学习的应用和价值
- 这有5种来自大自然「馈赠」的AI技术及其应用,你知道多少?
- 惊呆了!B 站公告低级错误,将公司名写成“百度”
- 从空军机长到大厂程序员,关系数据库之父传奇
- 多样性计算时代,怎样的技术生态才能满足发展需求
- 头秃,在线求名字:网易使用昵称交流,再也没有“哥,姐,总”
- 在不同浏览器上进行网页测试,结果是...... | 每日趣闻