函数wordpress模板

wordpress模板  时间:2021-04-13  阅读:()
Lua源码欣赏云风2013年4月17日畩畩注这是这本书中其中部分章节的草稿.
我不打算按顺序来编写这本书,而打算以独立章节的形式分开完成,到最后再做统一的调整.
由于业余时间不多,所以产出也不固定.
畩畩畩畩當目录第一章概览1由甮由源文件划分甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甮甲代码风格甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲由甮申界畵畡核心甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申由甮甴代码翻译及预编译字节码甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申由甮电内嵌库甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴由甮甶独立解析器及字节码编译器甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴由甮男阅读源代码的次序甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴第二章全局状态机及内存管理7甲甮由内存管理甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甲甮甲全局状态机甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甲甮甲甮由畧畡畲畢畡畧略畣畯畬畬略畣畴甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甲甲甮甲甮甲畳略略畤甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由申甲甮甲甮申畢畵甋甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由申甲甮甲甮甴當略畲畳畩畯畮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由申甲甮甲甮电防止初始化的意外甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甴第三章字符串17申甮由数据结构甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由男申甮由甮由畈畡畳畨畄畯畓甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甸申甮甲实现甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲田申甮甲甮由字符串比较甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲田申甮甲甮甲短字符串的内部化甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲田申甮申畕畳略畲畤畡畴畡的构造甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲甲第四章表25甴甮由数据结构甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲电甴甮甲算法甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲男甴甮甲甮由短字符串优化甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲甹甴甮甲甮甲数字类型的哈希值甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申由甴甮申表的迭代甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申申甴甮甴对元方法的优化甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申甶當當畩目录甴甮甴甮由类型名字甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申男第五章函数与闭包39电甮由函数原型甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申甹电甮甲畕異當畡畬畵略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴由电甮申闭包甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甲电甮申甮由界畵畡闭包甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴申电甮申甮甲畃闭包甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甶电甮申甮申轻量畃函数甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甶第六章协程及函数的执行49甶甮由栈与调用信息甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甹甶甮由甮由数据栈甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电田甶甮由甮甲调用栈甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甴甶甮由甮申线程甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甶甶甮甲线程的执行与中断甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甸甶甮甲甮由异常处理甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甸甶甮甲甮甲函数调用甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶田甶甮甲甮申钩子甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶甶甶甮甲甮甴从畃函数中挂起线程甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶男甶甮甲甮电挂起与延续甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶甸甶甮甲甮甶畬畵畡畣畡畬畬畫和畬畵畡異畣畡畬畬畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男申甶甮甲甮男异常处理甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甶第七章虚拟机77男甮由指令结构甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男男男甮由甮由常量甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甸男甮由甮甲操作码分类校验甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甹男甮由甮申操作码列表甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸田男甮甲字节码的运行甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸由男甮甲甮由畬畵畡畖略畸略畣畵畴略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸由男甮甲甮甲寄存器赋值甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸申男甮甲甮申表处理甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甶男甮甲甮甴表达式运算甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甹男甮甲甮电分支和跳转甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹电男甮甲甮甶函数调用甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甹男甮甲甮男不定长参数甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田由男甮甲甮甸生成闭包甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田申男甮甲甮甹畆畯畲循环甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甶男甮甲甮由田协程的中断和延续甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甸目录當畩畩第八章内置库的实现111甸甮由从畭畡畴畨模块看界畵畡的模块注册机制甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由由甸甮甲畭畡畴畨模块畁畐畉的实现甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由申甸甮申畳畴畲畩畮畧模块甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由电甸甮甴暂且搁置甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甶當畩畩畩目录第一章概览界畵畡是一门编程语言,界畵畡官方网站1提供了由语言发明者实现的官方版本2.
虽然界畵畡有简洁清晰的语言标准,但我们不能将语言的标准制定和实现完全分开看待.
事实上、随着官方实现版本的不断更新,界畵畡语言标准也在不断变化.
本书试图向读者展现界畵畡官方实现的细节.
在开始前,先从宏观上来看看,实现这门语言需要完成那些部分的工作.
界畵畡作为一门动态语言,提供了一个虚拟机.
界畵畡代码最终都是是以字节码的形式由虚拟机解释执行的.
把外部组织好的代码置入内存,让虚拟机解析运行,需要有一个源代码解释器,或是预编译字节码的加载器.
而只实现语言特性,几乎做不了什么事.
所以界畵畡的官方版本还提供了一些库,并提供一系列畃畁畐畉,供第三方开发.
这样,界畵畡就可以借助这些外部库,做一些我们需要的工作.
下面,我们按这个划分来分拆解析.
1.
1源文件划分从官网下载到界畵畡电甮甲的源代码后3,展开压缩包,会发现源代码文件全部放在畳畲畣子目录下.
这些文件根据实现功能的不同,可以分为四部分.
4由甮虚拟机运转的核心功能lapi.
c畃语言接口lctype.
c畃标准库中畣畴畹異略相关实现ldebug.
c畄略畢畵畧接口ldo.
c函数调用以及栈管理lfunc.
c函数原型及闭包管理lgc.
c垃圾回收lmem.
c内存管理接口lobject.
c对象操作的一些函数lopcodes.
c虚拟机的字节码定义lstate.
c全局状态机1Lua是一个以MITlicense发布的开源项目,你可以自由的下载、传播、使用它.
它的官方网站是:http://www.
lua.
org2Lua官方实现并不是对Lua语言的唯一实现.
另外比较流行的Lua语言实现还有LuaJIT(http://www.
luajit.
org).
由于采用了JIT技术,运行性能更快.
除此之外,还能在互联网上找到其它一些不同的实现.
3本书讨论的Lua5.
2.
2版可以在http://www.
lua.
org/ftp/lua-5.
2.
2.
tar.
gz下载获得4在LuaWiki上有一篇文章介绍了Lua源代码的结构:http://lua-users.
org/wiki/LuaSource由甲第一章概览lstring.
c字符串池ltable.
c表类型的相关操作ltm.
c元方法lvm.
c虚拟机lzio.
c输入流接口甲甮源代码解析以及预编译字节码lcode.
c代码生成器ldump.
c序列化预编译的界畵畡字节码llex.
c词法分析器lparser.
c解析器lundump.
c还原预编译的字节码申甮内嵌库lauxlib.
c库编写用到的辅助函数库lbaselib.
c基础库lbitlib.
c位操作库lcorolib.
c协程库ldblib.
c畄略畢畵畧库linit.
c内嵌库的初始化liolib.
c畉畏库lmathlib.
c数学库loadlib.
c动态扩展库管理loslib.
c畏畓库lstrlib.
c字符串库ltablib.
c表处理库甴甮可执行的解析器,字节码编译器lua.
c解释器luac.
c字节码编译器1.
2代码风格界畵畡使用畃畬略畡畮畃畛电畝5编写的源代码模块划分清晰,大部分模块被分解在不同的甮畣文件中实现,以同名的甮畨文件描述模块导出的接口.
比如,畬畳畴畲畩畮畧甮畣实现了界畵畡虚拟机中字符串池的相关功能,而这部分的内部接口则在畬畳畴畲畩畮畧甮畨中描述.
5CleanC是标准C/C++的一个子集.
它只包含了C语言中的一些必要特性.
这样方便把Lua发布到更多的可能对C语言支持不完整的平台上.
比如,对于没有ctype.
h的C语言编译环境,Lua提供了lctype.
c实现了一些兼容函数.
由甮申界畕畁核心申它使用的代码缩进风格比较接近畋甦畒风格6,并有些修改,例如函数定义的开花括号没有另起一行.
同时,也搀杂了一些畇畎畕风格,比如采用了双空格缩进、在函数名和小括号间加有空格.
代码缩进风格没有好坏,但求统一.
界畵畡的内部模块暴露出来的畁畐畉以畬畵畡畘畸畸畸风格命名,即畬畵畡后跟一个大写字母表识内部模块名,而后由下划线加若干小写字母描述方法名.
供外部程序使用的畁畐畉则使用畬畵畡畸畸畸的命名风格.
这些在界畵畡的官方文档里有详细描述.
定义在畬畵畡甮畨文件中.
此外,除了供库开发用的畬畵畡界系列畁畐畉(定义在畬畡畵畸畬畩畢甮畨中)外,其它都属于内部畁畐畉,禁止被外部程序使用7.
1.
3Lua核心界畵畡核心部分仅包括界畵畡虚拟机的运转.
界畵畡虚拟机的行为是由一组组畯異畣畯畤略控制的.
这些畯異畣畯畤略定义在畬畯異畣畯畤略畳甮畨及畬畯異畣畯畤略畳甮畣中.
而虚拟机对畯異畣畯畤略的解析和运作在畬當畭甮畣中,其畁畐畉以畬畵畡畖为前缀.
界畵畡虚拟机的外在数据形式是一个畬畵畡畓畴畡畴略结构体,取名畓畴畡畴略大概意为界畵畡虚拟机的当前状态.
全局畓畴畡畴略引用了整个虚拟机的所有数据.
这个全局畓畴畡畴略的相关代码放在畬畳畴畡畴略甮畣中,畁畐畉使用畬畵畡畅为前缀.
函数的运行流程:函数调用及返回则放在畬畤畯甮畣中,相关畁畐畉以畬畵畡畄8为前缀.
界畵畡中最复杂和重要的三种数据类型畦畵畮畣畴畩畯畮、畴畡畢畬略、畳畴畲畩畮畧的实现分属在畬畦畵畮畣甮畣、畬畴畡畢畬略甮畣、畬畳畴畲畩畮畧甮畣中.
这三组内部畁畐畉分别以畬畵畡畆、畬畵畡畈9、畬畵畡畓为前缀命名.
不同的数据类型最终被统一定义为界畵畡畏畢番略畣畴,相关的操作在畬畯畢番略畣畴甮畣中,畁畐畉以畬畵畡畏为前缀.
界畵畡从第电版后增加了元表,元表的处理在畬畴畭甮畣中,畁畐畉以畬畵畡畔为前缀.
另外,核心系统还用到两个基础设施:内存管理畬畭略畭甮畣,畁畐畉以畬畵畡畍为前缀;带缓冲的流处理畬畺畩畯甮畣,畁畐畉以畬畵畡畚为前缀.
最后是核心系统里最为复杂的部分,垃圾回收部分,在畬畧畣甮畣中实现,畁畐畉以畬畵畡畃为前缀.
界畵畡设计的初衷之一就为了最好的和宿主系统相结合.
它是一门嵌入式语言畛电畝,所以必须提供和宿主系统交互的畁畐畉.
这些畁畐畉以畃函数的形式提供,大多数实现在畬畡異畩甮畣中.
畁畐畉直接以畬畵畡为前缀,可供畃编写的程序库直接调用.
以上这些就构成了让畬畵畡运转起来的最小代码集合.
我们将在后面的章节来剖析其中的细节.
1.
4代码翻译及预编译字节码光有核心代码和一个虚拟机还无法让界畵畡程序运行起来.
因为必须从外部输入将运行的界畵畡程序.
界畵畡的程序的人读形式是一种程序文本,需要经过解析得到内部的数据结构(常量和畯異畣畯畤略的集合).
这个过程是通过異畡畲畳略畲:畬異畡畲畳略畲甮畣(畬畵畡留10为前缀的畁畐畉)及词法分析畬畬略畸甮畣(畬畵畡畘为前缀的畁畐畉).
解析完文本代码,还需要最终生成虚拟机理解的数据,这个步骤在畬畣畯畤略甮畣中实现,其畁畐畉以畬畵畡畋为前缀.
6K&R风格将左花括号放在行尾,而右花括号独占一行.
只对函数定义时有所例外.
GNU风格左右花括号均独占一行,且把TAB缩进改为两个空格[8].
不同的代码缩进风格还有许多其它细微差异.
阅读不同开源项目的代码将会有所体会.
如果要参于到某个开源项目的开发,通常应尊重该项目的代码缩进风格,新增和修改保持一致.
7理论上luaL系列API属于更高层次,对于Lua的第三方库开发也不是必须的.
这些API全部由Lua标准API实现,没有使用任何其它内部API.
8D取Do之意.
9H是意取Hash之意.
10Y可能是取yacc的含义.
因为Lua最早期版本的代码翻译是用yacc和lex这两个Unix工具实现的[4],后来才改为手写解析器.
甴第一章概览为了满足某些需求,加快代码翻译的流程.
还可以采用预编译的过程.
把运行时编译的结果,生成为字节码.
这个过程以及逆过程由畬畤畵畭異甮畣和畬畵畮畤畵畭異甮畣实现.
其畁畐畉以畬畵畡畕为前缀.
111.
5内嵌库作为嵌入式语言,其实完全可以不提供任何库及函数.
全部由宿主系统注入到畓畴畡畴略中即可.
也的确有许多系统是这么用的.
但界畵畡的官方版本还是提供了少量必要的库.
尤其是一些基础函数如異畡畩畲畳、略畲畲畯畲、畳略畴畭略畴畡畴畡畢畬略、畴畹異略等等,完成了语言的一些基本特性,几乎很难不使用.
而畣畯畲畯畵畴畩畮略、畳畴畲畩畮畧、畴畡畢畬略、畭畡畴畨等等库,也很常用.
界畵畡提供了一套简洁的方案,允许你自由加载你需要的部分,以控制最终执行文件的体积和内存的占用量.
主动加载这些内建库进入畬畵畡畓畴畡畴略,是由在畬畵畡畬畩畢甮畨中的畁畐畉实现的.
12在界畵畡电甮田之前,界畵畡并没有一个统一的模块管理机制.
这是由早期界畵畡仅仅定位在嵌入式语言决定的.
这些年,由更多的人倾向于把界畵畡作为一门独立编程语言来使用,那么统一的模块化管理就变得非常有必要.
这样才能让丰富的第三方库可以协同工作.
即使是当成嵌入式语言使用,随着代码编写规模的扩大,也需要合理的模块划分.
界畵畡电甮由引入了一个官方推荐的模块管理机制.
使用畲略畱畵畩畲略甯畭畯畤畵畬略来管理界畵畡模块,并允许从畃语言编写的动态库中加载扩展模块.
这个机制被作者认为有点过度设计了畛申畝.
在界畵畡电甮甲中又有所简化.
我们可以在畬畯畡畤畬畩畢甮畣中找到实现.
内建库的初始化畁畐畉则在畬畩畮畩畴甮畣中可以找到.
其它基础库可以在那些以畬畩畢甮畣为后缀的源文件中,分别找到它们的实现.
1.
6独立解析器及字节码编译器界畵畡在早期几乎都是被用来嵌入到其它系统中使用,所以源代码通常被编译成动态库或静态库被宿主系统加载或链接.
但随着界畵畡的第三方库越来越丰富,人们开始倾向于把界畵畡作为一门独立语言来使用.
界畵畡的官方版本里也提供了一个简单的独立解析器,便是畬畵畡甮畣所实现的这个.
并有畬畵畡畣甮畣实现了一个简单的字节码编译器,可以预编译文本的界畵畡源程序.
131.
7阅读源代码的次序界畵畡的源代码有着良好的设计,优美易读.
其整体篇幅不大,仅两万行畃代码左右14.
但一开始入手阅读还是有些许难度的.
从易到难,理清作者编写代码的脉络非常重要.
界畵畡畊畉畔的作者畍畩畫略畐畡畬畬在回答"哪一个开源代码项目设计优美,值得阅读不容错过"这个问题时,推荐了一个阅读次序15:首先、阅读外围的库是如何实现功能扩展的,这样可以熟悉界畵畡公开畁畐畉.
不必陷入功能细节.
然后、阅读畁畐畉的具体实现.
界畵畡对外暴露的畁畐畉可以说是一个对内部模块的一层封装,这个层次尚未触及核心,但可以对核心代码有个初步的了解.
11极端情况下,我们还可以对Lua的源码做稍许改变,把parser从最终的发布版本中裁减掉,让虚拟机只能加载预编译好的字节码.
这样可以减少执行代码的体积.
Lua的代码解析部分与核心部分之间非常独立,做到这一点所需修改极少.
但这种做法并不提倡.
12如果你静态链接Lua库,还可以通过这些API控制最终链入执行文件的代码体积.
13笔者倾向于在服务器应用中使用独立的Lua解析器.
这样会更加灵活,可以随时切换其它Lua实现(例如采用性能更高的LuaJIT),并可以方便的使用第三方库.
14Lua5.
2.
2版本的源代码分布在58个文件中,共20220行C代码.
15AskReddit:WhichOSScodebasesouttherearesowelldesignedthatyouwouldconsiderthem'mustreads'http://www.
reddit.
com/comments/63hth/ask_reddit_which_oss_codebases_out_there_are_so/c02pxbp由甮男阅读源代码的次序电之后、可以开始了解界畵畡畖畍的实现.
接下来就是分别理解函数调用、返回,畳畴畲畩畮畧、畴畡畢畬略、畭略畴畡畴畡畢畬略等如何实现.
畤略畢畵畧模块是一个额外的设施,但可以帮助你理解界畵畡内部细节.
最后是異畡畲畳略畲等等编译相关的部分.
垃圾收集将是最难的部分,可能会花掉最多的时间去理解细节.
16本书接下来的章节,并未按照以上次序来撰写.
但大多数篇章能独立阅读,相互参考之处均有附注.
读者可根据需要,自由选择阅读次序.
16笔者曾经就Lua5.
1.
4的gc部分做过细致的剖析.
相关文章可以在这里找到:http://blog.
codingnow.
com/2011/04/lua_gc_multithreading.
html,在本书的后面,会重新领略Lua5.
2.
2的实现.
甶第一章概览第二章全局状态机及内存管理界畵畡可以方便的被嵌入畃程序中使用.
你可以很容易的创建出一个界畵畡虚拟机对象,不同的界畵畡虚拟机之间的工作是线程安全的,因为一切和虚拟机相关的内存操作都被关联到虚拟机对象中,而没有利用任何其它共享变量.
界畵畡的虚拟机核心部分,没有任何的系统调用,是一个纯粹的黑盒子,正确的使用界畵畡,不会对系统造成任何干扰.
这其中最关键的一点是,界畵畡让用户自行定义内存管理器,在创建界畵畡虚拟机时传入,这保证了界畵畡的整个运行状态是用户可控的.
2.
1内存管理界畵畡要求用户给出一个内存管理函数,在界畵畡创建虚拟机的时候传入.
.
由typedefvoid*(*lua_Alloc)(void*ud,void*ptr,size_tosize,size_tnsize);甲申LUA_APIlua_State*(lua_newstate)(lua_Allocf,void*ud);虽然许多时候,我们并不直接使用畬畵畡畮略畷畳畴畡畴略这个畁畐畉,而是用另一个更方便的版本畬畵畡界畮略畷畳畴畡畴略.
但从畁畐畉命名就可以看出,后者不输入核心畁畐畉,它是利用前者实现的.
它利用畃标准库中的函数实现了一个默认的内存管理器,这也可以帮助我们理解这个内存管理器的语义.
源代码甲甮由町畬畡畵畸畬畩畢甮畣町畬畵畡界畮略畷畳畴畡畴略甹由甹staticvoid*l_alloc(void*ud,void*ptr,size_tosize,size_tnsize){甹甲田(void)ud;(void)osize;/*notused*/甹甲由if(nsize==0){甹甲甲free(ptr);甹甲申returnNULL;甹甲甴}甹甲电else甹甲甶returnrealloc(ptr,nsize);甹甲男}甹甲甸甹甲甹甹申田staticintpanic(lua_State*L){男甸第二章全局状态机及内存管理甹申由luai_writestringerror("PANIC:unprotectederrorincalltoLuaAPI(%s)\n",甹申甲lua_tostring(L,-1));甹申申return0;/*returntoLuatoabort*/甹申甴}甹申电甹申甶甹申男LUALIB_APIlua_State*luaL_newstate(void){甹申甸lua_State*L=lua_newstate(l_alloc,NULL);甹申甹if(L)lua_atpanic(L,&panic);甹甴田returnL;甹甴由}界畵畡定义的内存管理器仅有一个函数,虽然接口上类似畲略畡畬畬畯畣但是和畃标准库中的畲略畡畬畬畯畣有所区别.
它需要在畮畳畩畺略为田时,提供释放内存的功能.
和标准库中的内存管理接口不同,界畵畡在调用它的时候会准确给出内存块的原始大小.
对于需要为界畵畡定制一个高效的内存管理器来说,这个信息很重要.
因为大多数内存管理的算法,都需要在释放内存的时候了解内存块的尺寸.
而标准库的畦畲略略和畲略畡畬畬畯畣函数却不给出这个信息.
所以大多数内存管理模块在实现时,在内存块的前面加了一个畣畯畯畫畩略,把内存块尺寸存放在里面.
界畵畡在实现时,刻意提供了这个信息.
这样我们不必额外储存内存块尺寸,这对大量使用小内存块的环境可以节约不少内存.
另外,这个内存管理接口接受一个额外的指针畵畤.
这可以让内存管理模块工作在不同的堆上.
恰当的定制内存管理器,就可以回避线程安全问题.
不考虑线程安全的因素,我们可以让内存管理工作更为高效.
另一个可以辅助我们做出更有效的内存管理模块的机制是在界畵畡电甮甲时增加的.
当異畴畲传入畎畕界界时,畯畳畩畺略的含义变成了对象的类型.
这样,分配器就可以得知我们是在分配一个什么类型的对象,可以针对它做一些统计或优化工作.
界畵畡使用了一组宏来管理不同类别的内存:单个对象、数组、可变长数组等.
这组宏定义在畬畭略畭甮畨中.
源代码甲甮甲町畬畭略畭甮畨町畭略畭畯畲畹甲甴#defineluaM_reallocv(L,b,on,n,e)\甲电(cast(void,\甲甶(cast(size_t,(n)+1)>MAX_SIZET/(e))(luaM_toobig(L),0):0),\甲男luaM_realloc_(L,(b),(on)*(e),(n)*(e)))甲甸甲甹#defineluaM_freemem(L,b,s)luaM_realloc_(L,(b),(s),0)申田#defineluaM_free(L,b)luaM_realloc_(L,(b),sizeof(*(b)),0)申由#defineluaM_freearray(L,b,n)luaM_reallocv(L,(b),n,0,sizeof((b)[0]))申甲申申#defineluaM_malloc(L,s)luaM_realloc_(L,NULL,0,(s))申甴#defineluaM_new(L,t)cast(t*,luaM_malloc(L,sizeof(t)))申电#defineluaM_newvector(L,n,t)\甲甮由内存管理甹申甶cast(t*,luaM_reallocv(L,NULL,0,n,sizeof(t)))申男申甸#defineluaM_newobject(L,tag,s)luaM_realloc_(L,NULL,tag,(s))申甹甴田#defineluaM_growvector(L,v,nelems,size,t,limit,e)\甴由if((nelems)+1>(size))\甴甲((v)=cast(t*,luaM_growaux_(L,v,&(size),sizeof(t),limit,e)))甴申甴甴#defineluaM_reallocvector(L,v,oldn,n,t)\甴电((v)=cast(t*,luaM_reallocv(L,v,oldn,n,sizeof(t))))这组宏的核心在一个内部畁畐畉:畬畵畡畍畲略畡畬畬畯畣,它不会被直接调用.
其实现在畬畭略畭甮畣中.
它调用保存在畧畬畯畢畡畬畓畴畡畴略中的内存分配器管理内存.
这些工作不仅仅是分配新的内存,释放不用的内存,扩展不够用的内存.
界畵畡也会通过畲略畡畬畬畯畣试图释放掉预申请过大的内存的后半部分,当然,这取决于用户提供的内存管理器能不能缩小内存块了.
之所以用宏来实现这组畁畐畉,是因为内存管理会被高频调用,而畬畵畡畍畲略畡畬畬畯畣當这样的畁畐畉的传入参数中经常出现常数,实现为宏可以保证常量计算在编译时进行.
源代码甲甮申町畬畭略畭甮畣町畲略畡畬畬畯畣男电void*luaM_realloc_(lua_State*L,void*block,size_tosize,size_tnsize){男甶void*newblock;男男global_State*g=G(L);男甸size_trealosize=(block)osize:0;男甹lua_assert((realosize==0)==(block==NULL));甸田#ifdefined(HARDMEMTESTS)甸由if(nsize>realosize&&g->gcrunning)甸甲luaC_fullgc(L,1);/*forceaGCwheneverpossible*/甸申#endif甸甴newblock=(*g->frealloc)(g->ud,block,osize,nsize);甸电if(newblock==NULL&&nsize>0){甸甶api_check(L,nsize>realosize,甸男"realloccannotfailwhenshrinkingablock");甸甸if(g->gcrunning){甸甹luaC_fullgc(L,1);/*trytofreesomememory.
.
.
*/甹田newblock=(*g->frealloc)(g->ud,block,osize,nsize);/*tryagain*/甹由}甹甲if(newblock==NULL)甹申luaD_throw(L,LUA_ERRMEM);甹甴}甹电lua_assert((nsize==0)==(newblock==NULL));甹甶g->GCdebt=(g->GCdebt+nsize)-realosize;由田第二章全局状态机及内存管理甹男returnnewblock;甹甸}从代码中可以看到,畬畵畡畍畲略畡畬畬畯畣根据传入的畯畳畩畺略和畮畳畩畺略调整内部感知的内存大小(设置畇畃畤略畢畴),在内存不够用的时候会主动尝试做畇畃操作.
畬畭略畭甮畣中还有另一个内部畁畐畉畬畵畡畍畧畲畯畷畡畵畸,它是用来管理可变长数组的.
其主要策略是当数组空间不够时,扩大为原来的两倍.
源代码甲甮甴町畬畭略畭甮畣町畧畲畯畷當略畣畴畯畲甴甶void*luaM_growaux_(lua_State*L,void*block,int*size,size_tsize_elems,甴男intlimit,constchar*what){甴甸void*newblock;甴甹intnewsize;电田if(*size>=limit/2){/*cannotdoubleit*/电由if(*size>=limit)/*cannotgrowevenalittle*/电甲luaG_runerror(L,"toomany%s(limitis%d)",what,limit);电申newsize=limit;/*stillhaveatleastonefreeplace*/电甴}电电else{电甶newsize=(*size)*2;电男if(newsize(size))\申((v)=cast(t*,luaM_growaux_(L,v,&(size),sizeof(t),limit,e)))2.
2全局状态机从界畵畡的使用者的角度看,畧畬畯畢畡畬畓畴畡畴略是不可见的.
我们无法用公开的畁畐畉取到它的指针,也不需要引用它.
但分析界畵畡的实现就不能绕开这个部分.
畧畬畯畢畡畬畓畴畡畴略里面有对主线程的引用,有注册表管理所有全局数据,有全局字符串表,有内存管理函数,有畇畃需要的把所有对象串联起来的相关信息,以及一切界畵畡在工作时需要的工作内存.
通过畬畵畡畮略畷畳畴畡畴略创建一个新的界畵畡虚拟机时,第一块申请的内存将用来保存主线程和这个全局状态机.
界畵畡的实现尽可能的避免内存碎片,同时也减少内存分配和释放的次数.
它采用了一个小技巧,利用一个界畇结构,把主线程畬畵畡畓畴畡畴略和畧畬畯畢畡畬畓畴畡畴略分配在一起.
甲甮甲全局状态机由由源代码甲甮电町畬畳畴畡畴略甮畣町界畇电甹typedefstructLX{甶田#ifdefined(LUAI_EXTRASPACE)甶由charbuff[LUAI_EXTRASPACE];甶甲#endif甶申lua_Statel;甶甴}LX;甶电甶甶甶男/*甶甸**Mainthreadcombinesathreadstateandtheglobalstate甶甹*/男田typedefstructLG{男由LXl;男甲global_Stateg;男申}LG;这里,主线程必须定义在结构的前面,否则关闭虚拟机的时候(如下代码)就无法正确的释放内存.
由(*g->frealloc)(g->ud,fromstate(L),sizeof(LG),0);/*freemainblock*/这里的界畇结构是放在畃文件内部定义的,而不存在公开的畈文件中,是仅供这一个畃代码了解的知识,所以这种依赖数据结构内存布局的用法负作用不大.
畬畵畡畮略畷畳畴畡畴略这个公开畁畐畉定义在畬畳畴畡畴略甮畣中,它初始化了所有畧畬畯畢畡畬畓畴畡畴略中将引用的数据.
阅读它的实现可以了解全局状态机中到底包含了哪些东西.
源代码甲甮甶町畬畳畴畡畴略甮畣町畬畵畡畮略畷畳畴畡畴略甲甶甲LUA_APIlua_State*lua_newstate(lua_Allocf,void*ud){甲甶申inti;甲甶甴lua_State*L;甲甶电global_State*g;甲甶甶LG*l=cast(LG*,(*f)(ud,NULL,LUA_TTHREAD,sizeof(LG)));甲甶男if(l==NULL)returnNULL;甲甶甸L=&l->l.
l;甲甶甹g=&l->g;甲男田L->next=NULL;甲男由L->tt=LUA_TTHREAD;甲男甲g->currentwhite=bit2mask(WHITE0BIT,FIXEDBIT);甲男申L->marked=luaC_white(g);甲男甴g->gckind=KGC_NORMAL;甲男电preinit_state(L,g);甲男甶g->frealloc=f;甲男男g->ud=ud;由甲第二章全局状态机及内存管理甲男甸g->mainthread=L;甲男甹g->seed=makeseed(L);甲甸田g->uvhead.
u.
l.
prev=&g->uvhead;甲甸由g->uvhead.
u.
l.
next=&g->uvhead;甲甸甲g->gcrunning=0;/*noGCwhilebuildingstate*/甲甸申g->GCestimate=0;甲甸甴g->strt.
size=0;甲甸电g->strt.
nuse=0;甲甸甶g->strt.
hash=NULL;甲甸男setnilvalue(&g->l_registry);甲甸甸luaZ_initbuffer(L,&g->buff);甲甸甹g->panic=NULL;甲甹田g->version=lua_version(NULL);甲甹由g->gcstate=GCSpause;甲甹甲g->allgc=NULL;甲甹申g->finobj=NULL;甲甹甴g->tobefnz=NULL;甲甹电g->sweepgc=g->sweepfin=NULL;甲甹甶g->gray=g->grayagain=NULL;甲甹男g->weak=g->ephemeron=g->allweak=NULL;甲甹甸g->totalbytes=sizeof(LG);甲甹甹g->GCdebt=0;申田田g->gcpause=LUAI_GCPAUSE;申田由g->gcmajorinc=LUAI_GCMAJOR;申田甲g->gcstepmul=LUAI_GCMUL;申田申for(i=0;imt[i]=NULL;申田甴if(luaD_rawrunprotected(L,f_luaopen,NULL)!
=LUA_OK){申田电/*memoryallocationerror:freepartialstate*/申田甶close_state(L);申田男L=NULL;申田甸}申田甹else申由田luai_userstateopen(L);申由由returnL;申由甲}2.
2.
1garbagecollect大部分内容和畇畃有关.
因为内存管理和不用的内存回收都是基于整个虚拟机的,采用根扫描的畇畃算法的界畕畁实现自然把所有畇畃管理下内存的根与畇畃过程的关联信息都保存在了这里.
有关畇畃的代码是界畵畡源码中最复杂的部分,将单列一章来分析.
甲甮甲全局状态机由申2.
2.
2seed由g->seed=makeseed(L);畳略略畤和字符串的畨畡畳畨算法相关,参见申甮由甮由节.
2.
2.
3bu由luaZ_initbuffer(L,&g->buff);畢畵甋用于界畵畡源代码文本的解析过程,以及字符串处理需要的临时空间.
界畵畡的单个畓畴畡畴略工作在单线程模式下,所以在内部需要时,总是重复利用这个指针指向的临时空间.
2.
2.
4version由g->version=lua_version(NULL);这是界畵畡电甮甲新增加的特性.
让我们先看一下畬畵畡當略畲畳畩畯畮的实现.
源代码甲甮男町畬畡異畩甮畣町畬畵畡當略畲畳畩畯畮由申男LUA_APIconstlua_Number*lua_version(lua_State*L){由申甸staticconstlua_Numberversion=LUA_VERSION_NUM;由申甹if(L==NULL)return&version;由甴田elsereturnG(L)->version;由甴由}这里把一个全局变量地址赋给了當略畲畳畩畯畮域.
这样可以起到一个巧妙的作用:用户可以在运行时获得传入参数界中的當略畲畳畩畯畮地址,与自身链入的畬畵畡虚拟机实现代码中的这个变量,知道创建这个界的代码是否和自身的代码版本是否一致.
这是比版本号比较更强的检查.
我们再看看畬畵畡界畣畨略畣畫當略畲畳畩畯畮的实现就能明白畬畵畡當略畲畳畩畯畮这个畁畐畉为何不返回一个版本号数字,而是给出一个保存了版本号数字的内存指针了.
1由#defineluaL_checkversion(L)luaL_checkversion_(L,LUA_VERSION_NUM)源代码甲甮甸町畬畡畵畸畬畩畢甮畣町畬畵畡界畣畨略畣畫當略畲畳畩畯畮甹甴甴LUALIB_APIvoidluaL_checkversion_(lua_State*L,lua_Numberver){甹甴电constlua_Number*v=lua_version(L);甹甴甶if(v!
=lua_version(NULL))甹甴男luaL_error(L,"multipleLuaVMsdetected");甹甴甸elseif(*v!
=ver)甹甴甹luaL_error(L,"versionmismatch:app.
needs%f,Luacoreprovides%f",1luaLcheckversion一个重要的功能是检测多重链入的lua虚拟机.
这固然是错误使用lua造成的,并非lua自身的缺陷.
但把lua作用一门嵌入式语言而不是独立语言使用是lua的设计初衷,这个bug非常常见且不易发觉,所以给出一个api来检测错误的发生是个不错的设计.
具体分析可以参考笔者的一篇bloghttp://blog.
codingnow.
com/2012/01/lua_link_bug.
html.
同时在4.
1节也有进一步的讨论.
由甴第二章全局状态机及内存管理甹电田ver,*v);甹电由/*checkconversionsnumber->integertypes*/甹电甲lua_pushnumber(L,-(lua_Number)0x1234);甹电申if(lua_tointeger(L,-1)!
=-0x1234||甹电甴lua_tounsigned(L,-1)!
=(lua_Unsigned)-0x1234)甹电电luaL_error(L,"badconversionnumber->int;"甹电甶"mustrecompileLuawithpropersettings");甹电男lua_pop(L,1);甹电甸}2.
2.
5防止初始化的意外界畵畡在处理虚拟机创建的过程非常的小心.
由于内存管理器是外部传入的,不可能保证它的返回结果.
到底有多少内存可供使用也是未知数.
为了保证界畵畡虚拟机的健壮性,需要检查所有的内存分配结果.
界畵畡自身有完整的异常处理机制可以处理这些错误.
所以界畵畡的初始化过程是分两步进行的,首先初始化不需要额外分配内存的部分,把异常处理机制先建立起来,然后去调用可能引用内存分配失败导致错误的初始化代码.
由if(luaD_rawrunprotected(L,f_luaopen,NULL)!
=LUA_OK){甲/*memoryallocationerror:freepartialstate*/申close_state(L);甴L=NULL;电}甶else男luai_userstateopen(L);在甶甮甲甮由节,会展示畬畵畡畄畲畡畷畲畵畮異畲畯畴略畣畴略畤怎样截获内存分配的异常情况.
我们现在仅关注畦畬畵畡畯異略畮函数.
源代码甲甮甹町畬畳畴畡畴略甮畣町畦畬畵畡畯異略畮由甸申staticvoidf_luaopen(lua_State*L,void*ud){由甸甴global_State*g=G(L);由甸电UNUSED(ud);由甸甶stack_init(L,L);/*initstack*/由甸男init_registry(L,g);由甸甸luaS_resize(L,MINSTRTABSIZE);/*initialsizeofstringtable*/由甸甹luaT_init(L);由甹田luaX_init(L);由甹由/*pre-creatememory-errormessage*/由甹甲g->memerrmsg=luaS_newliteral(L,MEMERRMSG);由甹申luaS_fix(g->memerrmsg);/*itshouldneverbecollected*/由甹甴g->gcrunning=1;/*allowgc*/由甹电}甲甮甲全局状态机由电这里初始化了主线程的数据栈2、初始化注册表、给出一个基本的字符串池3、初始化元表用的字符串4、初始化词法分析用的畴畯畫略畮串5、初始化内存错误信息.
2主线程是独立于其它线程的创建过程直接创建出来的.
并没有调用luanewthread.
3luaSresize参见3.
2.
2节4luaTinit参见4.
4节5luaXinit.
由甶第二章全局状态机及内存管理第三章字符串界畵畡中的字符串可以包含任何甸位字符,包括了畃语言中标示字符串结束的\田.
它以带长度的内存块的形式在内部保存字符串,同时在和畃语言做交互时,又能保证在每个内部储存的字符串末尾添加\田以兼容畃库函数.
这使得界畵畡的字符串应用范围相当广.
界畵畡管理及操作字符串的方式和畃语言不太相同,通过阅读其实现代码,可以加深对界畵畡字符串的理解,能更为高效的使用它.
3.
1数据结构从界畵畡电甮甲甮由开始,字符串保存在界畵畡状态机内有两种内部形式,短字符串及长字符串.
源代码申甮由町畬畯畢番略畣畴甮畨町當畡畲畩畡畮畴畳畴畲畩畮畧电电/*Varianttagsforstrings*/电甶#defineLUA_TSHRSTR(LUA_TSTRING|(0>5)+1;/*ifstringistoolong,don'thashallitschars*/申size_tl1;甴for(l1=l;l1>=step;l1-=step)/*computehash*/电h=h^((h>2)+cast(unsignedchar,str[l1-1]));界畵畡电甮甲甮田发布后不久,有人在邮件列表中提出,界畵畡的这个设计有可能对其给于畈畡畳畨畄畯畓攻击的机会5.
攻击者可以轻易构造出上千万拥有相同哈希值的不同字符串,以此数十倍的降低界畵畡从外部压入字符串进入内部字符串表的效率畛甶畝.
当界畵畡用于大量依赖字符串处理的诸如畈畔畔畐服务的处理时,输入的字符串不可控制,很容易被人恶意利用.
2对于短字符串extra用来记录这个字符串是否为保留字,这个标记用于词法分析器对保留字的快速判断;对于长字符串,可以用于惰性求哈希值.
3合并相同的字符串可以大量减少内存占用,缩短比较字符串的时间.
因为相同的字符串只需要保存一份在内存中,当用这个字符串做键匹配时,比较字符串只需要比较地址是否相同就够了,而不必逐字节比较.
4关于长字符串惰性求哈希值的过程,参见4.
2.
1节.
5Real-WorldImpactofHashDoSinLua.
http://lua-users.
org/lists/lua-l/2012-01/msg00497.
html申甮由数据结构由甹界畵畡电甮甲甮由为了解决这个问题,把长字符串独立出来.
大量文本处理中的输入的字符串不再通过哈希内部化进入全局字符串表.
同时,使用了一个随机种子用于哈希值的计算,使攻击者无法轻易构造出拥有相同哈希值的不同字符串.
源代码申甮电町畬畳畴畲畩畮畧甮畣町畳畴畲畩畮畧畨畡畳畨电由unsignedintluaS_hash(constchar*str,size_tl,unsignedintseed){电甲unsignedinth=seed^cast(unsignedint,l);电申size_tl1;电甴size_tstep=(l>>LUAI_HASHLIMIT)+1;电电for(l1=l;l1>=step;l1-=step)电甶h=h^((h>2)+cast_byte(str[l1-1]));电男returnh;电甸}这个随机种子是在界畵畡畓畴畡畴略创建时放在全局表中的,它利用了构造状态机的内存地址随机性以及用户可配置的一个随机量同时来决定6.
源代码申甮甶町畬畳畴畡畴略甮畣町畭畡畫略畳略略畤甸田/*甸由**Computeaninitialseedasrandomaspossible.
InANSI,relyon甸甲**AddressSpaceLayoutRandomization(ifpresent)toincrease甸申**randomness.
.
甸甴*/甸电#defineaddbuff(b,p,e)\甸甶{size_tt=cast(size_t,e);\甸男memcpy(buff+p,&t,sizeof(t));p+=sizeof(t);}甸甸甸甹staticunsignedintmakeseed(lua_State*L){甹田charbuff[4*sizeof(size_t)];甹由unsignedinth=luai_makeseed();甹甲intp=0;甹申addbuff(buff,p,L);/*heapvariable*/甹甴addbuff(buff,p,&h);/*localvariable*/甹电addbuff(buff,p,luaO_nilobject);/*globalvariable*/甹甶addbuff(buff,p,&lua_newstate);/*publicfunction*/甹男lua_assert(p==sizeof(buff));甹甸returnluaS_hash(buff,p,h);甹甹}6用户可以在luaconf.
h中配置luai_makeseed定义自己的随机方法,默认是利用time函数获取时间构造种子.
值得注意的是,使用系统当前时间来构造随机种子这种行为,有可能给调试带来一些困扰.
因为字符串hash值的不同,会让程序每次运行过程中的内部布局有一些细微变化.
好在字符串池使用的是开散列算法(参见3.
2.
2),这个影响非常的小.
但如果你希望让嵌入lua的程序,每次运行都严格一致,最好自己定义luai_makeseed函数.
甲田第三章字符串3.
2实现3.
2.
1字符串比较比较两个字符串是否相同,需要区分长短字符串.
子类型不同自然不是相同的字符串.
源代码申甮男町畬畳畴畲畩畮畧甮畣町略畱畳畴畲甴电intluaS_eqstr(TString*a,TString*b){甴甶return(a->tsv.
tt==b->tsv.
tt)&&甴男(a->tsv.
tt==LUA_TSHRSTReqshrstr(a,b):luaS_eqlngstr(a,b));甴甸}长字符串比较,当长度不同时,自然是不同的字符串.
而长度相同时,则需要逐字节比较:源代码申甮甸町畬畳畴畲畩畮畧甮畣町略畱畬畮畧畳畴畲申申intluaS_eqlngstr(TString*a,TString*b){申甴size_tlen=a->tsv.
len;申电lua_assert(a->tsv.
tt==LUA_TLNGSTR&&b->tsv.
tt==LUA_TLNGSTR);申甶return(a==b)||/*sameinstanceor.
.
.
*/申男((len==b->tsv.
len)&&/*equallengthand.
.
.
*/申甸(memcmp(getstr(a),getstr(b),len)==0));/*equalcontents*/申甹}短字符串因为经过的内部化,所以不必比较字符串内容,而仅需要比较对象地址即可.
界畵畡用一个宏来高效的实现它:源代码申甮甹町畬畳畴畲畩畮畧甮畨町略畱畳畨畲畳畴畲申甴#defineeqshrstr(a,b)check_exp((a)->tsv.
tt==LUA_TSHRSTR,(a)==(b))3.
2.
2短字符串的内部化所有的短字符串都被内部化放在全局的字符串表中.
这张表是用一个哈希表来实现7.
源代码申甮由田町畬畳畴畲畩畮畧甮畣町畩畮畴略畲畮畳畨畲畳畴畲由申申staticTString*internshrstr(lua_State*L,constchar*str,size_tl){由申甴GCObject*o;由申电global_State*g=G(L);由申甶unsignedinth=luaS_hash(str,l,g->seed);由申男for(o=g->strt.
hash[lmod(h,g->strt.
size)];由申甸o!
=NULL;由申甹o=gch(o)->next){由甴田TString*ts=rawgco2ts(o);7字符串表和Lua表中的哈希部分(源代码4.
4)不同,需求更简单.
它不必迭代.
全局只有一个,不用太考虑空间利用率.
所以这里使用的是开散列算法.
开散列也被称为Separatechaining[10].
即,将哈系值相同的对象串在分别独立的链表中,实现起来更为简单.
申甮甲实现甲由由甴由if(h==ts->tsv.
hash&&由甴甲l==ts->tsv.
len&&由甴申(memcmp(str,getstr(ts),l*sizeof(char))==0)){由甴甴if(isdead(G(L),o))/*stringisdead(butwasnotcollectedyet)*/由甴电changewhite(o);/*resurrectit*/由甴甶returnts;由甴男}由甴甸}由甴甹returnnewshrstr(L,str,l,h);/*notfound;createanewstring*/由电田}这是一个开散列的哈希表实现.
一个字符串被放入字符串表的时候,先检查一下表中有没有相同的字符串.
如果有,则复用已有的字符串;没有则创建一个新的.
碰到哈希值相同的字符串,简单的串在同一个哈希位的链表上即可.
注意由甴甴甭由甴电行,这里需要检查表中的字符串是否是死掉的字符串.
这是因为界畵畡的垃圾收集过程是分步完成的.
而向字符串池添加新字符串在任何步骤之间都可能发生.
有可能在标记完字符串后发现有些字符串没有任何引用,但在下个步骤中又产生了相同的字符串导致这个字符串复活.
当哈希表中字符串的数量用畮畵畳略域甩超过预定容量用畳畩畺略域甩时.
可以预计畨畡畳畨冲突必然发生.
这个时候就调用luaS_resize方法把字符串表的哈希链表数组扩大,重新排列所有字符串的位置.
这个过程和界畵畡表(参见源代码甴甮电)的处理类似,不过里面涉及垃圾收集的一些细节,不在本节分析.
源代码申甮由由町畬畳畴畲畩畮畧甮畣町畲略畳畩畺略甶甴voidluaS_resize(lua_State*L,intnewsize){甶电inti;甶甶stringtable*tb=&G(L)->strt;甶男/*cannotresizewhileGCistraversingstrings*/甶甸luaC_runtilstate(L,~bitmask(GCSsweepstring));甶甹if(newsize>tb->size){男田luaM_reallocvector(L,tb->hash,tb->size,newsize,GCObject*);男由for(i=tb->size;ihash[i]=NULL;男甲}男申/*rehash*/男甴for(i=0;isize;i++){男电GCObject*p=tb->hash[i];男甶tb->hash[i]=NULL;男男while(p){/*foreachnodeinthelist*/男甸GCObject*next=gch(p)->next;/*savenext*/男甹unsignedinth=lmod(gco2ts(p)->hash,newsize);/*newposition*/甸田gch(p)->next=tb->hash[h];/*chainit*/甸由tb->hash[h]=p;甸甲resetoldbit(p);/*seeMOVEOLDrule*/甲甲第三章字符串甸申p=next;甸甴}甸电}甸甶if(newsizesize){甸男/*shrinkingslicemustbeempty*/甸甸lua_assert(tb->hash[newsize]==NULL&&tb->hash[tb->size-1]==NULL);甸甹luaM_reallocvector(L,tb->hash,tb->size,newsize,GCObject*);甹田}甹由tb->size=newsize;甹甲}每在界畵畡状态机内部创建一个字符串,都会按畃风格字符串存放,以兼容畃接口.
即在字符串的末尾加上一个\田,这在畬畳畴畲畩畮畧甮畣的由田甸行可以见到.
这样不违背界畵畡自己用内存块加长度的方式储存字符串的规则,在把界畵畡字符串传递出去和畃语言做交互时,又不必做额外的转换.
源代码申甮由甲町畬畳畴畲畩畮畧甮畣町畣畲略畡畴略畳畴畲畯畢番甹甸staticTString*createstrobj(lua_State*L,constchar*str,size_tl,甹甹inttag,unsignedinth,GCObject**list){由田田TString*ts;由田由size_ttotalsize;/*totalsizeofTStringobject*/由田甲totalsize=sizeof(TString)+((l+1)*sizeof(char));由田申ts=&luaC_newobj(L,tag,totalsize,list,0)->ts;由田甴ts->tsv.
len=l;由田电ts->tsv.
hash=h;由田甶ts->tsv.
extra=0;由田男memcpy(ts+1,str,l*sizeof(char));由田甸((char*)(ts+1))[l]='\0';/*ending0*/由田甹returnts;由由田}3.
3Userdata的构造畕畳略畲畤畡畴畡在界畵畡中并没有太特别的地方,在储存形式上和字符串相同.
可以看成是拥有独立元表,不被内部化处理,也不需要追加\田的字符串.
在实现上,只是对象结构从畔畓畴畲畩畮畧换成了畕畄畡畴畡.
所以实现代码也被放在畬畳畴畲畩畮畧甮畣中,其畡異畩也以畬畵畡畓开头.
源代码申甮由申町畬畯畢番略畣畴甮畨町畵畤畡畴畡甴申由typedefunionUdata{甴申甲L_Umaxaligndummy;/*ensuresmaximumalignmentfor'local'udata*/甴申申struct{甴申甴CommonHeader;申甮申畕畓畅畒畄畁畔畁的构造甲申甴申电structTable*metatable;甴申甶structTable*env;甴申男size_tlen;/*numberofbytes*/甴申甸}uv;甴申甹}Udata;畕畳略畲畤畡畴畡的数据部分和字符串一样,紧接在这个结构后面,并没有单独分配内存.
畕畳略畲畤畡畴畡的构造函数如下:源代码申甮由甴町畬畳畴畲畩畮畧甮畣町畮略畷畵畤畡畴畡由男电Udata*luaS_newudata(lua_State*L,size_ts,Table*e){由男甶Udata*u;由男男if(s>MAX_SIZET-sizeof(Udata))由男甸luaM_toobig(L);由男甹u=&luaC_newobj(L,LUA_TUSERDATA,sizeof(Udata)+s,NULL,0)->u;由甸田u->uv.
len=s;由甸由u->uv.
metatable=NULL;由甸甲u->uv.
env=e;由甸申returnu;由甸甴}甲甴第三章字符串第四章表界畵畡使用畴畡畢畬略作为统一的数据结构.
在一次对界畵畡作者的采访中1,他们这样说道:Roberto:从我的角度,灵感来自于VDM(一个主要用于软件规范的形式化方法),当我们开始创建Lua时,有一些东西引起了我的兴趣.
VDM提供三种数据聚合的方式:set、sequence和map.
不过,set和sequence都很容易用map来表达,因此我有了用map作为统一结构的想法.
Luiz也有他自己的原因.
Luiz:没错,我非常喜欢AWK,特别是它的联合数组.
4.
1数据结构用畴畡畢畬略来表示界畵畡中的一切数据结构是界畵畡语言的一大特色.
为了效率,界畵畡的官方实现,又把畴畡畢畬略的储存分为数组部分和哈希表部分.
数组部分从由开始作整数数字索引.
这可以提供紧凑且高效的随机访问.
而不能被储存在数组部分的数据全部放在哈希表中,唯一不能做哈希键值的是畮畩畬,这个限制可以帮助我们发现许多运行期错误.
界畵畡的哈希表有一个高效的实现,几乎可以认为操作哈希表的时间复杂度为畏用由甩.
这样分开的储存方案,对使用者是完全透明的,并没有强求界畵畡语言的实现一定要这样分开.
但在畬畵畡的基础库中,提供了異畡畩畲畳和畩異畡畩畲畳两个不同的畡異畩来实现两种不同方式的畴畡畢畬略遍历方案;用産对畴畡畢畬略取长度时,也被定义成和整数下标有关,而非整个畴畡畢畬略的尺寸.
在界畵畡的畃畁畐畉中,有独立的畡異畩来操作数组的整数下标部分.
也就是说,界畵畡有大量的特性,提供了对整数下标的高效访问途径.
畔畡畢畬略的内部数据结构被定义在畬畯畢番略畣畴甮畨中,源代码甴甮由町畬畯畢番略畣畴甮畨町畴畡畢畬略电甴电typedefunionTKey{电甴甶struct{电甴男TValuefields;电甴甸structNode*next;/*forchaining*/电甴甹}nk;电电田TValuetvk;电电由}TKey;电电甲电电申电电甴typedefstructNode{1见《MastermindsofProgramming:ConversationswiththeCreatorsofMajorProgrammingLanguages》的第7章,中译名《编程之魂》.
笔者曾做过这一章的翻译:http://blog.
codingnow.
com/2010/06/masterminds_of_programming_7_lua.
html甲电甲甶第四章表电电电TValuei_val;电电甶TKeyi_key;电电男}Node;电电甸电电甹电甶田typedefstructTable{电甶由CommonHeader;电甶甲lu_byteflags;/*1lsizenode))畔畡畢畬略的数组部分被储存在畔畖畡畬畵略甪畡畲畲畡畹中,其长度信息存于畩畮畴畳畩畺略畡畲畲畡畹.
哈希表储存在畎畯畤略甪畮畯畤略,哈希表的大小用lu_bytelsizenode表示,由于哈希表的大小一定为甲的整数次幂,所以这里的畬畳畩畺略畮畯畤略表示的是幂次,而不是实际大小.
每个畴畡畢畬略结构,最多会由三块连续内存构成.
一个畔畡畢畬略结构,一块存放了连续整数索引的数组,和一块大小为甲的整数次幂的哈希表.
哈希表的最小尺寸为甲的田次幂,也就是由.
为了减少空表的维护成本,界畵畡在这里做了一点优化.
它定义了一个不可改写的空哈希表:畤畵畭畭畹畮畯畤略.
让空表被初始化时,畮畯畤略域指向这个畤畵畭畭畹节点.
它虽然是一个全局变量,但因为对其访问是只读的,所以不会引起线程安全问题.
2源代码甴甮甲町畬畴畡畢畬略甮畣町畤畵畭畭畹畮畯畤略甶男#definedummynode(&dummynode_)2不当的链接lua库,有可能造成错误.
如果你错误的链接了两份相同的Lua库实现到你的进程中,大多数情况下,代码可以安全的运行.
但在清除空table时,会因为无法正确的识别dummynode而程序崩溃.
建议在Lua扩展库的实现时调用luaL_checkversion做一下检查.
更详细的分析参见笔者的一篇bloghttp://blog.
codingnow.
com/2012/01/lua_link_bug.
html.
甴甮甲算法甲男甶甸甶甹#defineisdummy(n)((n)==dummynode)男田男由staticconstNodedummynode_={男甲{NILCONSTANT},/*value*/男申{{NILCONSTANT,NULL}}/*key*/男甴};阅读luaH_new和luaH_free两个畡異畩的实现,可以了解这一层次的数据结构.
源代码甴甮申町畬畴畡畢畬略甮畣町畮略畷申甶甸Table*luaH_new(lua_State*L){申甶甹Table*t=&luaC_newobj(L,LUA_TTABLE,sizeof(Table),NULL,0)->h;申男田t->metatable=NULL;申男由t->flags=cast_byte(~0);申男甲t->array=NULL;申男申t->sizearray=0;申男甴setnodevector(L,t,0);申男电returnt;申男甶}申男男申男甸申男甹voidluaH_free(lua_State*L,Table*t){申甸田if(!
isdummy(t->node))申甸由luaM_freearray(L,t->node,cast(size_t,sizenode(t)));申甸甲luaM_freearray(L,t->array,t->sizearray);申甸申luaM_free(L,t);申甸甴}其中畳略畴畮畯畤略當略畣畴畯畲用来初始化哈希表部分.
内存管理部分则使用了畬畵畡畍相关畡異畩.
4.
2算法畔畡畢畬略按照畬畵畡语言的定义,需要实现四种基本操作:读、写、迭代和获取长度.
畬畵畡中并没有删除操作,而仅仅是把对应键位的值设置为畮畩畬.
写操作被实现为查询已有键位,若不存在则创建新键.
得到键位后,写入操作就是一次赋值.
所以,在畴畡畢畬略模块中,实际实现的基本操作为:创建、查询、迭代和获取长度.
创建操作的畡異畩为luaH_newkey,阅读它的实现就能对整个畴畡畢畬略有一个全面的认识.
它只负责在哈希表中创建出一个不存在的键,而不关数组部分的工作.
因为畴畡畢畬略的数组部分操作和畃语言数组没有什么不同,不需要特别处理.
源代码甴甮甴町畬畴畡畢畬略甮畣町畮略畷畫略畹甴田电TValue*luaH_newkey(lua_State*L,Table*t,constTValue*key){甲甸第四章表甴田甶Node*mp;甴田男if(ttisnil(key))luaG_runerror(L,"tableindexisnil");甴田甸elseif(ttisnumber(key)&&luai_numisnan(L,nvalue(key)))甴田甹luaG_runerror(L,"tableindexisNaN");甴由田mp=mainposition(t,key);甴由由if(!
ttisnil(gval(mp))||isdummy(mp)){/*mainpositionistaken*/甴由甲Node*othern;甴由申Node*n=getfreepos(t);/*getafreeplace*/甴由甴if(n==NULL){/*cannotfindafreeplace*/甴由电rehash(L,t,key);/*growtable*/甴由甶/*whatevercalled'newkey'takecareofTMcacheandGCbarrier*/甴由男returnluaH_set(L,t,key);/*insertkeyintogrowntable*/甴由甸}甴由甹lua_assert(!
isdummy(n));甴甲田othern=mainposition(t,gkey(mp));甴甲由if(othern!
=mp){/*iscollidingnodeoutofitsmainposition*/甴甲甲/*yes;movecollidingnodeintofreeposition*/甴甲申while(gnext(othern)!
=mp)othern=gnext(othern);/*findprevious*/甴甲甴gnext(othern)=n;/*redothechainwith'n'inplaceof'mp'*/甴甲电*n=*mp;/*copycollidingnodeintofreepos.
(mp->nextalsogoes)*/甴甲甶gnext(mp)=NULL;/*now'mp'isfree*/甴甲男setnilvalue(gval(mp));甴甲甸}甴甲甹else{/*collidingnodeisinitsownmainposition*/甴申田/*newnodewillgointofreeposition*/甴申由gnext(n)=gnext(mp);/*chainnewposition*/甴申甲gnext(mp)=n;甴申申mp=n;甴申甴}甴申电}甴申甶setobj2t(L,gkey(mp),key);甴申男luaC_barrierback(L,obj2gco(t),key);甴申甸lua_assert(ttisnil(gval(mp)));甴申甹returngval(mp);甴甴田}畬畵畡的哈希表以闭散列3方式实现.
每个可能的键值,在哈希表中都有一个主要位置,称作畭畡畩畮異畯畳畩畴畩畯畮.
创建一个新键时,检查畭畡畩畮異畯畳畩畴畩畯畮,若无人使用,则可以直接设置为这个新键.
若之前有其它键占据了3用闭散列方法解决哈希表的键冲突,往往可以让哈希表内数据更为紧凑,有更高的空间利用率.
关于其算法,可以参考:http://en.
wikipedia.
org/wiki/Open_addressing甴甮甲算法甲甹这个位置,则检查占据此位置的键的主位置是不是这里.
若两者位置冲突,则利用畎畯畤略结构中的畮略畸畴域,以一个单向链表的形式把它们链起来;否则,新键占据这个位置,而老键更换到新位置并根据它的主键找到属于它的链的那条单向链表中上一个结点,重新链入.
无论是哪种冲突情况,都需要在哈希表中找到一个空闲可用的结点.
这里是在畧略畴畦畲略略異畯畳函数中,递减畬畡畳畴畦畲略略域来实现的.
畬畵畡也不会在设置键位的值为畮畩畬时而回收空间,而是在预先准备好的哈希空间使用完后惰性回收.
即在畬畡畳畴畦畲略略递减到哈希空间头时,做一次畲略畨畡畳畨操作.
源代码甴甮电町畬畴畡畢畬略甮畣町畲略畨畡畳畨申甴申staticvoidrehash(lua_State*L,Table*t,constTValue*ek){申甴甴intnasize,na;申甴电intnums[MAXBITS+1];/*nums[i]=numberofkeyswith2^(i-1)tsv.
extra==0){/*nohash*/由田甴s->tsv.
hash=luaS_hash(getstr(s),s->tsv.
len,s->tsv.
hash);由田电s->tsv.
extra=1;/*nowithasitshash*/由田甶}由田男returnhashstr(t,rawtsvalue(key));由田甸}由田甹caseLUA_TSHRSTR:由由田returnhashstr(t,rawtsvalue(key));由由由caseLUA_TBOOLEAN:由由甲returnhashboolean(t,bvalue(key));由由申caseLUA_TLIGHTUSERDATA:由由甴returnhashpointer(t,pvalue(key));由由电caseLUA_TLCF:由由甶returnhashpointer(t,fvalue(key));由由男default:由由甸returnhashpointer(t,gcvalue(key));由由甹}由甲田}从luaH_get的实现中可以看到,遇到短字符串查询时,就会去调用luaH_getstr回避逐字节的字符串比较操作.
源代码甴甮男町畬畴畡畢畬略甮畣町畧略畴甴甶申/*甴甶甴**searchfunctionforshortstrings甴甶电*/甴甶甶constTValue*luaH_getstr(Table*t,TString*key){甴甶男Node*n=hashstr(t,key);甴甶甸lua_assert(key->tsv.
tt==LUA_TSHRSTR);甴甮甲算法申由甴甶甹do{/*checkwhether'key'issomewhereinthechain*/甴男田if(ttisshrstring(gkey(n))&&eqshrstr(rawtsvalue(gkey(n)),key))甴男由returngval(n);/*that'sit*/甴男甲elsen=gnext(n);甴男申}while(n);甴男甴returnluaO_nilobject;甴男电}甴男甶甴男男甴男甸/*甴男甹**mainsearchfunction甴甸田*/甴甸由constTValue*luaH_get(Table*t,constTValue*key){甴甸甲switch(ttype(key)){甴甸申caseLUA_TSHRSTR:returnluaH_getstr(t,rawtsvalue(key));甴甸甴caseLUA_TNIL:returnluaO_nilobject;甴甸电caseLUA_TNUMBER:{甴甸甶intk;甴甸男lua_Numbern=nvalue(key);甴甸甸lua_number2int(k,n);甴甸甹if(luai_numeq(cast_num(k),n))/*indexisint*/甴甹田returnluaH_getint(t,k);/*usespecializedversion*/甴甹由/*elsegothrough*/甴甹甲}甴甹申default:{甴甹甴Node*n=mainposition(t,key);甴甹电do{/*checkwhether'key'issomewhereinthechain*/甴甹甶if(luaV_rawequalobj(gkey(n),key))甴甹男returngval(n);/*that'sit*/甴甹甸elsen=gnext(n);甴甹甹}while(n);电田田returnluaO_nilobject;电田由}电田甲}电田申}4.
2.
2数字类型的哈希值当数字类型为键,且没有置入数组部分时,我们需要对它们取哈希值,便于放进哈希表内.
在界畵畡电甮由之前,对数字类型的哈希运算非常简单.
就是对其占用的内存块的数据,按整数形式相加,代码如下:申甲第四章表由#definenumintscast_int(sizeof(lua_Number)/sizeof(int))甲申staticNode*hashnum(constTable*t,lua_Numbern){甴unsignedinta[numints];电inti;甶if(luai_numeq(n,0))/*avoidproblemswith-0*/男returngnode(t,0);甸memcpy(a,&n,sizeof(a));甹for(i=1;i甲甸甲#include甲甸申甲甸甴#defineluai_hashnum(i,n){inte;\甲甸电n=l_mathop(frexp)(n,&e)*(lua_Number)(INT_MAX-DBL_MAX_EXP);\甲甸甶lua_number2int(i,n);i+=e;}甲甸男甲甸甸#endif4.
3表的迭代在界畵畡中,并没有提供一个自维护状态的迭代器.
而是给出了一个畮略畸畴方法.
传入上一个键,返回下一个键值对.
这就是luaH_next所要实现的.
源代码甴甮由由町畬畴畡畢畬略甮畣町畮略畸畴由甶甹intluaH_next(lua_State*L,Table*t,StkIdkey){由男田inti=findindex(L,t,key);/*findoriginalelement*/由男由for(i++;isizearray;i+tryfirstarraypart*/由男甲if(!
ttisnil(&t->array[i]anon-nilvalue*/由男申setnvalue(key,cast_num(i+1));由男甴setobj2s(L,key+1,&t->array[i]);由男电return1;由男甶}由男男}由男甸for(i-=t->sizearray;isizearray)/*is'key'insidearraypart*/由甴甹returni-1;/*yes;that'stheindex(correctedtoC)*/由电田else{由电由Node*n=mainposition(t,key);由电甲forcheckwhether'key'issomewhereinthechain*/由电申/*keymaybedeadalready,butitisoktouseitin'next'*/由电甴if(luaV_rawequalobj(gkey(n),key)||由电电(ttisdeadkey(gkey(n))&&iscollectable(key)&&由电甶deadvalue(gkey(n))==gcvalue(key))){由电男i=cast_int(n-gnode(t,0));/*keyindexinhashtable*/由电甸/*hashelementsarenumberedafterarrayones*/由电甹returni+t->sizearray;由甶田}由甶由elsen=gnext(n);由甶甲if(n==NULL)由甶申luaG_runerror(L,"invalidkeyto"LUA_QL("next"));/*keynotfound*/由甶甴}由甶电}由甶甶}畬畵畡的畴畡畢畬略的长度定义只对序列表有效9.
所以,在实现的时候,仅需要遍历畴畡畢畬略的数组部分.
只有6从实现上看,在遍历过程中插入一个不存在的键,并不会让程序崩溃,但有可能在当次遍历过程中,无法遍历到这个新插入的键.
更严重的后果是,新插入的键触发了rehash过程,很有可能遍历到曾经遍历过的节点.
7在Lua手册[5]关于next的描述中,这样写道:Thebehaviorofnextisundenedif,duringthetraversal,youassignanyvaluetoanon-existenteldinthetable.
Youmayhowevermodifyexistingelds.
Inparticular,youmayclearexistingelds.
8死键在rehash后会从哈希表中清除,而不添加新键就不会rehash表而导致死键消失.
在这个前提下,遍历table是安全的.
9在Lua5.
1时,对table的获取长度定义更为严格一些.
一个tablet的长度n,要保证t[n]不为空,且t[n+1]一定为空.
t[1]为空的table的长度可定义为零.
到了lua5.
2后,在手册[5]的3.
4.
6节简化了定义.
在循序序列中出现空洞(即有空值)有可能影响长度计算,但并非空值一定甴甮申表的迭代申电当数组部分填满时才需要进一步的去检索哈希表.
它使用二分法,来快速在哈希表中快速定位一个非空的整数键的位置.
源代码甴甮由申町畬畴畡畢畬略甮畣町畧略畴畮电申甲staticintunbound_search(Table*t,unsignedintj){电申申unsignedinti=j;/*iiszeroorapresentindex*/电申甴j++;电申电/*find'i'and'j'suchthatiispresentandjisnot*/电申甶while(!
ttisnil(luaH_getint(t,j))){电申男i=j;电申甸j*=2;电申甹if(j>cast(unsignedint,MAX_INT)){/*overflow*/电甴田/*tablewasbuiltwithbadpurposes:resorttolinearsearch*/电甴由i=1;电甴甲while(!
ttisnil(luaH_getint(t,i)))i++;电甴申returni-1;电甴甴}电甴电}电甴甶/*nowdoabinarysearchbetweenthem*/电甴男while(j-i>1){电甴甸unsignedintm=(i+j)/2;电甴甹if(ttisnil(luaH_getint(t,m)))j=m;电电田elsei=m;电电由}电电甲returni;电电申}电电甴电电电电电甶/*电电男**Trytofindaboundaryintable't'.
A'boundary'isanintegerindex电电甸**suchthatt[i]isnon-nilandt[i+1]isnil(and0ift[1]isnil).
电电甹*/电甶田intluaH_getn(Table*t){电甶由unsignedintj=t->sizearray;电甶甲if(j>0&&ttisnil(&t->array[j-1])){电甶申/*thereisaboundaryinthearraypart:(binary)searchforit*/电甶甴unsignedinti=0;电甶电while(j-i>1){电甶甶unsignedintm=(i+j)/2;电甶男if(ttisnil(&t->array[m-1]))j=m;电甶甸elsei=m;会截断长度统计.
申甶第四章表电甶甹}电男田returni;电男由}电男甲/*elsemustfindaboundaryinhashpart*/电男申elseif(isdummy(t->node))/*hashpartisempty*/电男甴returnj;/*thatiseasy.
.
.
*/电男电elsereturnunbound_search(t,j);电男甶}4.
4对元方法的优化界畵畡实现复杂数据结构,大量依赖给畴畡畢畬略附加一个元表(畭略畴畡畴畡畢畬略)来实现.
故而畴畡畢畬略本身的一大作用就是作为元表存在.
查询元表中是否存在一个特定的元方法就很容易成为运行期效率的热点.
如果不能高效的解决这个热点,每次对带有元表的畴畡畢畬略的操作,都需要至少多作一次畨畡畳畨查询.
但是,并非所有元表都提供了所有元方法的,对于不存在的元方法查询就是一个浪费了.
在源代码甴甮由中,我们可以看到,每个畔畡畢畬略结构中都有一个甍畡畧畳域.
它记录了那些元方法不存在.
畬畴畡畢畬略甮畨里定义了一个宏:由#defineinvalidateTMcache(t)((t)->flags=0)这个宏用来在畴畡畢畬略被修改时,清空这组标记位,强迫重新做元方法查询.
只要充当元表10的畴畡畢畬略没有被修改,缺失元方法这样的查询结果就可以缓存在这组标记位中了.
源代码甴甮由甴町畬畴畭甮畨町畦畡畳畴畴畭甴由#definegfasttm(g,et,e)((et)==NULLNULL:\甴甲((et)->flags&(1utmname[e]))甴申甴甴#definefasttm(l,et,e)gfasttm(G(l),et,e)我们可以看到畦畡畳畴畴畭这个宏能够快速的剔除不存在的元方法.
另一个优化点是,不必在每次做元方法查询的时候都压入元方法的名字.
在畳畴畡畴略初始化时,畬畵畡对这些元方法生成了字符串对象:源代码甴甮由电町畬畴畭甮畣町畩畮畩畴申甲voidluaT_init(lua_State*L){申申staticconstchar*constluaT_eventnameORDERTM*/申甴"__index","__newindex",申电"__gc","__mode","__len","__eq",申甶"__add","__sub","__mul","__div","__mod",申男"__pow","__unm","__lt","__le",10lua5.
2仅对table的元表做了这个优化,而没有理会其它类型的元表的元方法查询.
这大概是因为,只有table容易缺失一些诸如__index这样的元方法,而使用table的默认行为.
当lua代码把这些操作作用于其它类型如userdata时,它没有table那样的默认行为,故对应的元方法通常存在.
甴甮甴对元方法的优化申男申甸"__concat","__call"申甹};甴田inti;甴由for(i=0;itmname[i]=luaS_new(L,luaT_eventname[i]);甴申luaS_fix(G(L)->tmname[i]);/*nevercollectthesenames*/甴甴}甴电}这样,在畴畡畢畬略查询这些字符串要比我们使用lua_getfield这样的外部畡異畩要快的多11.
通过调用luaT_gettmbyobj可以获得需要的元方法.
源代码甴甮由甶町畬畴畭甮畣町畧略畴畴畭甶申constTValue*luaT_gettmbyobj(lua_State*L,constTValue*o,TMSevent){甶甴Table*mt;甶电switch(ttypenv(o)){甶甶caseLUA_TTABLE:甶男mt=hvalue(o)->metatable;甶甸break;甶甹caseLUA_TUSERDATA:男田mt=uvalue(o)->metatable;男由break;男甲default:男申mt=G(L)->mt[ttypenv(o)];男甴}男电return(mtluaH_getstr(mt,G(L)->tmname[event]):luaO_nilobject);男甶}4.
4.
1类型名字最后,有一段和元方法不太相关的代码也放在畬畴畭模块中12.
在畬畴畭甮畨甯畬畴畭甮畣中还为每个畬畵畡类型提供了字符串描述.
它用于输出调试信息以及作为lua_typename的返回值.
这个字符串并未在其它场合用到,所以也没有为其预生成畳畴畲畩畮畧对象.
源代码甴甮由男町畬畴畭甮畣町畴畹異略畮畡畭略甲甲staticconstcharudatatypename[]="userdata";甲申甲甴LUAI_DDEFconstchar*constluaT_typenames_[LUA_TOTALTAGS]={甲电"novalue",甲甶"nil","boolean",udatatypename,"number",甲男"string","table","function",udatatypename,"thread",11使用lua_getfield做字符串检索,需要先将字符串压入state.
这意味着需要把外部字符串在短字符串表池中做一次哈希查询.
12把这组字符串常量定义在ltm中,大概是因为这里同时定义了元方法的名字常量,实现比较类似罢了.
申甸第四章表甲甸"proto","upval"/*theselasttwocasesareusedfortestsonly*/甲甹};畵畤畡畴畡畴畹異略畮畡畭略在这里单独并定义出来,多半出于严谨的考虑.
让畵畳略畲畤畡畴畡和畬畩畧畨畴畵畳略畲畤畡畴畡返回的產畵畳略畲畤畡畴畡產字符串指针保持一致.
第五章函数与闭包闭包(畃畬畯畳畵畲略)这个概念对畃语言1程序员比较陌生.
但在函数式编程中却是一个重要概念.
如果说畃甫甫式的面向对象编程是把一组函数绑定到特定数据类型上的话,那么闭包可以说是把一组数据绑定到特定函数上.
一个简单的闭包是这样的:由functionMakeCounter()甲localt=0申returnfunction()甴t=t+1电returnt甶end男end当调用畍畡畫略畃畯畵畮畴略畲后,会得到一个函数.
这个函数每调用一次,返回值就会递增一.
顾名思义,我们可以把这个返回的函数看作一个计数器.
畍畡畫略畃畯畵畮畴略畲可以产生多个计数器,每个都独立计数.
也就是说,每个计数器函数都独享一个变量畴,相互不干扰.
这个畴被称作计数器函数的畵異當畡畬畵略,被绑定到计数器函数中.
拥有了畵異當畡畬畵略的函数就是闭包.
在界畵畡电甮由之前,所有的函数都实现成闭包.
只是从语义上来说,畵異當畡畬畵略数量为零的闭包被称为函数.
看这样一段代码:由functionfoobar()甲returnfunction()申return"Hello"甴end电end畦畯畯畢畡畲每次调用都会生成一个新的、没有畵異當畡畬畵略的闭包,即便这些闭包在功能上完全相同.
界畵畡电甮甲改进了这一点.
它尽量复用拥有完全相同畵異當畡畬畵略的闭包.
对于没有畵異當畡畬畵略的函数来说,也就不会再出现第二份重复的对象了.
5.
1函数原型闭包是函数和畵異當畡畬畵略的结合体.
在界畵畡中,闭包统一被称为函数,而函数就是这里所指的函数原型.
原型是不可以直接被调用的,只有和畵異當畡畬畵略结合在一起才变成了界畵畡语言中供用户使用的函数对象.
所1C语言缺乏支持闭包的基本语法机制,但也有人为它做了语法扩展.
Apple公司为C提供了一个叫作Blocks的非标准扩展,可以让C语言支持闭包[7].
申甹甴田第五章函数与闭包以,从公开的畁畐畉定义中,是不存在函数原型这一数据类型的.
它只存在于实现中,但和字符串、表这些数据类型一样,参于垃圾回收的过程.
由functionfoobar()甲returnload[[申return"Hello"甴]]电end这段代码看似会得到和前面提到的代码相同的结果:每次调用畦畯畯畢畡畲都会生成一个返回畈略畬畬畯字符串的函数.
但实际上却有所区别.
畬畯畡畤不仅仅有编译代码的成本,而且每次会产生一个新的函数原型;而直接返回一个函数对象,无论是否需要绑定畵異當畡畬畵略,都会复用同一个函数原型.
生成函数原型有两个途径:其一是由源代码编译而来,其二可以从编译好的字节码加载得到.
这些都不在本章节细述,我们仅讨论把函数原型和畵異當畡畬畵略绑定在一起得到闭包的过程.
畐畲畯畴畯是一种畇畃畏畢番略畣畴,它的结构定义在畬畯畢番略畣畴甮畨中.
它的类型为界畕畁畔畐畒畏畔畏,由于这并不是一个公开类型,最终用户无法得到一个畐畲畯畴畯对象,所以界畕畁畔畐畒畏畔畏没有定义在畬畵畡甮畨中,而存在于畬畯畢番略畣畴甮畨.
由#defineLUA_TPROTOLUA_NUMTAGS源代码电甮由町畬畯畢番略畣畴甮畨町畐畲畯畴畯甴甶男typedefstructProto{甴甶甸CommonHeader;甴甶甹TValue*k;/*constantsusedbythefunction*/甴男田Instruction*code;甴男由structProto**p;/*functionsdefinedinsidethefunction*/甴男甲int*lineinfo;/*mapfromopcodestosourcelines(debuginformation)*/甴男申LocVar*locvars;/*informationaboutlocalvariables(debuginformation)*/甴男甴Upvaldesc*upvalues;/*upvalueinformation*/甴男电unionClosure*cache;/*lastcreatedclosurewiththisprototype*/甴男甶TString*source;/*usedfordebuginformation*/甴男男intsizeupvalues;/*sizeof'upvalues'*/甴男甸intsizek;/*sizeof'k'*/甴男甹intsizecode;甴甸田intsizelineinfo;甴甸由intsizep;/*sizeof'p'*/甴甸甲intsizelocvars;甴甸申intlinedefined;甴甸甴intlastlinedefined;甴甸电GCObject*gclist;甴甸甶lu_bytenumparams;/*numberoffixedparameters*/电甮甲畕畐畖畁界畕畅甴由甴甸男lu_byteis_vararg;甴甸甸lu_bytemaxstacksize;/*maximumstackusedbythisfunction*/甴甸甹}Proto;从数据结构看,畐畲畯畴畯记录了函数原型的字节码、函数引用的常量表、调试信息、和其它一些基本信息:有多少个参数,调用这个函数需要多大的数据栈空间.
参数个数和数据栈空间大小可以让虚拟机在运行的时候,可以精确预分配出需要的数据栈空间,这样就不必每次对栈访问的时候都去做越界检查了.
这个结构的细节在第七章提及时,还会讨论.
界畵畡的函数不像畃语言那样,只能平坦的定义2;界畵畡可以在函数中再定义函数,畐畲畯畴畯也就必然被实现为有层级的.
在畐畲畯畴畯结构中,我们能看到对内层畐畲畯畴畯的引用異.
从界畵畡电甮甲开始,界畵畡将原型和变量绑定的过程,都尽量避免重复生成不必要的闭包.
当生成一次闭包后,闭包将被畣畡畣畨略引用,下次再通过这个原型生成闭包时,比较畵異當畡畬畵略是否一致来决定复用.
畣畡畣畨略是一个弱引用,一旦在畧畣流程发现引用的闭包已不存在,畣畡畣畨略将被置空.
界畵畡的调试信息很丰富,调试信息占用的内存甚至多过字节码本身.
每条畣畯畤略中的指令都对应着畬畩畮略数组中的源代码行号.
局部变量和畵異當畡畬畵略的调试信息,包括名字和在源代码中的作用域都记录在畐畲畯畴畯结构里.
从结构定义的名字中很好理解.
嵌套畐畲畯畴畯、源代码、变量名这些都由畇畃管理生命期.
其它数据结构则和畐畲畯畴畯本身紧密绑定.
我们查看畬畵畡畆畦畲略略異畲畯畴畯的源代码就能读到在释放一个畐畲畯畴畯结构时,顺带回收了这些内部数据结构占用的内存.
由于界畵畡的内存管理需要提供每个内存块的大小3,在畐畲畯畴畯结构中也如实记录了它们.
源代码电甮甲町畬畦畵畮畣甮畣町畬畵畡畆畦畲略略異畲畯畴畯由申电voidluaF_freeproto(lua_State*L,Proto*f){由申甶luaM_freearray(L,f->code,f->sizecode);由申男luaM_freearray(L,f->p,f->sizep);由申甸luaM_freearray(L,f->k,f->sizek);由申甹luaM_freearray(L,f->lineinfo,f->sizelineinfo);由甴田luaM_freearray(L,f->locvars,f->sizelocvars);由甴由luaM_freearray(L,f->upvalues,f->sizeupvalues);由甴甲luaM_free(L,f);由甴申}5.
2Upvalue畕異當畡畬畵略4指在闭包生成的那一刻,与函数原型绑定在一起的那些外部变量.
这些变量原本是上一层函数的局部变量或畵異當畡畬畵略,可以在上层返回返回后,继续被闭包引用.
理解畵異當畡畬畵略并不太难,但畵異當畡畬畵略的实现却复杂的多.
当外层函数并没有退出时,我们调用刚生成的闭包,这个时候闭包更像一个普通的内嵌函数.
外层函数的局部变量只是数据栈上的一个普通变量,虚拟机用一个数据栈上的索引映射局部变量,内嵌函数可以通过2GCC对C语言的扩展允许嵌套定义函数.
但嵌套函数不等于闭包.
你可以从内层函数中访问外层函数中的局部变量,但如果你用函数指针去引用内层函数时,这些函数调用就不可靠了.
因为它引用的外层变量可能并不存在或是不是想像中的值.
3参见2.
1节.
4Upvalue没有特别恰当的中译名.
有人主张把upvalue直译为"上值",略显古怪.
笔者觉得译为"升量"也不错,意为被提升的变量,但恐怕也难被广泛接受.
故在本书中全部保留英文.
甴甲第五章函数与闭包数据栈自由访问它.
而一旦外层函数返回,数据栈空间收缩,原有的局部变量消失了.
这个时候闭包需要用其它方式访问这些畵異當畡畬畵略.
如果将数据栈上的每个变量都实现成一个独立的对象是没有必要的,尤其是数字、布尔量这类数据类型,没必要实现成复杂对象.
界畵畡没有采用这种低效的实现方法.
它的畵異當畡畬畵略从实现上来讲,更像是畃语言中的指针.
它引用了另一个对象.
多个闭包可以共享同一个畵異當畡畬畵略,有如畃语言中,可以有多份指针指向同一个结构体.
在界畵畡中,畵異當畡畬畵略是内部的一种独立的数据类型,表示对另一个界畵畡值的引用.
由#defineLUA_TUPVAL(LUA_NUMTAGS+1)畕異畖畡畬的结构定义在畬畯畢番略畣畴甮畨中:源代码电甮申町畬畯畢番略畣畴甮畨町畕異畖畡畬甴甹甶typedefstructUpVal{甴甹男CommonHeader;甴甹甸TValue*v;/*pointstostackortoitsownvalue*/甴甹甹union{电田田TValuevalue;/*thevalue(whenclosed)*/电田由struct{/*doublelinkedlist(whenopen)*/电田甲structUpVal*prev;电田申structUpVal*next;电田甴}l;电田电}u;电田甶}UpVal;它直接用一个畔畖畡畬畵略指针引用一个界畵畡值变量.
当被引用的变量还在数据栈上时,这个指针直接指向栈上的地址.
这个畵異當畡畬畵略被称为开放的.
由于界畵畡的数据栈的大小可扩展,当数据栈内存延展时,其内存地址会发生变化.
这个时候需要主动修正畕異畖畡畬结构中的指针.
这个过程我们在甶甮由甮由节提及,它由畬畤畯甮畣中的畣畯畲畲略畣畴畳畴畡畣畫函数来实现5.
遍历当前所有开放的畵異當畡畬畵略利用的是当前线程中记录的链表畯異略畮畵異當畡畬6.
这是一个双向链表,所以在畕異畖畡畬结构中有两个指针指向前后节点.
链表指针保存在一个联合中.
当畵異當畡畬畵略被关闭后,就不再需要这两个指针了.
所谓关闭畵異當畡畬畵略,就是当畵異當畡畬畵略引用的数据栈上的数据不再存在于栈上时(通常是由申请局部变量的函数返回引起的),需要把畵異當畡畬畵略从开放链表中拿掉,并把其引用的数据栈上的变量值换一个安全的地方存放.
这个安全所在就是畕異畖畡畬结构体内.
无须用特别的标记位区分一个畕異畖畡畬在开放还是关闭状态.
当畵異當畡畬畵略关闭时,畕異畖畡畬中的指针當一定指向结构内部的當畡畬畵略.
5.
3闭包界畵畡支持支持两种闭包:由界畵畡语言实现的,以及用畃语言实现的.
从外部数据类型来看,它们同属于一种类型:函数(界畵畡畔畆畕畎畃畔畉畏畎).
从畇畃管理的角度看,是同一种畇畃畏畢番略畣畴类型:5参见源代码6.
5.
6参见源代码6.
3.
电甮申闭包甴申由typedefunionClosure{甲CClosurec;申LClosurel;甴}Closure;根据界畵畡闭包或是畃闭包的不同,畃畬畯畳畵畲略定位为一个联合.
畃畃畬畯畳畵畲略和界畃畬畯畳畵畲略共有一个相同的数据头:由#defineClosureHeader\甲CommonHeader;lu_bytenupvalues;GCObject*gclist5.
3.
1Lua闭包界畵畡闭包仅仅是原型畐畲畯畴畯和畕異畖畡畬的集合.
源代码电甮甴町畬畯畢番略畣畴甮畨町界畃畬畯畳畵畲略电甲申typedefstructLClosure{电甲甴ClosureHeader;电甲电structProto*p;电甲甶UpVal*upvals[1];/*listofupvalues*/电甲男}LClosure;源代码电甮电町畬畦畵畮畣甮畣町畬畵畡畆畮略畷界畣畬畯畳畵畲略申田Closure*luaF_newLclosure(lua_State*L,intn){申由Closure*c=&luaC_newobj(L,LUA_TLCL,sizeLclosure(n),NULL,0)->cl;申甲c->l.
p=NULL;申申c->l.
nupvalues=cast_byte(n);申甴while(n--)c->l.
upvals[n]=NULL;申电returnc;申甶}构造界畵畡闭包的内部畁畐畉畬畵畡畆畮略畷界畣畬畯畳畵畲略只绑定了畐畲畯畴畯却不包括初始化畵異當畡畬畵略对象的过程.
这是因为构造界畵畡闭包有两种可能的途径.
第一、界畵畡闭包一般在虚拟机运行的过程中被动态构造出来的.
这时,闭包需引用的畵異當畡畬畵略都在当前的数据栈上.
利用畬畵畡畆甌畮畤畵異當畡畬这个畁畐畉可以把数据栈上的值转换为畵異當畡畬畵略.
源代码电甮甶町畬畦畵畮畣甮畣町畬畵畡畆甌畮畤畵異當畡畬甴男UpVal*luaF_findupval(lua_State*L,StkIdlevel){甴甸global_State*g=G(L);甴甹GCObject**pp=&L->openupval;电田UpVal*p;电由UpVal*uv;甴甴第五章函数与闭包电甲while(*pp!
=NULL&&(p=gco2uv(*pp))->v>=level){电申GCObject*o=obj2gco(p);电甴lua_assert(p->v!
=&p->u.
value);电电lua_assert(!
isold(o)||isold(obj2gco(L)));电甶if(p->v==level){/*foundacorrespondingupvalue*/电男if(isdead(g,o))/*isitdead*/电甸changewhite(o);/*resurrectit*/电甹returnp;甶田}甶由pp=&p->next;甶甲}甶申/*notfound:createanewone*/甶甴uv=&luaC_newobj(L,LUA_TUPVAL,sizeof(UpVal),pp,0)->uv;甶电uv->v=level;/*currentvaluelivesinthestack*/甶甶uv->u.
l.
prev=&g->uvhead;/*doublelinkitin'uvhead'list*/甶男uv->u.
l.
next=g->uvhead.
u.
l.
next;甶甸uv->u.
l.
next->u.
l.
prev=uv;甶甹g->uvhead.
u.
l.
next=uv;男田lua_assert(uv->u.
l.
next->u.
l.
prev==uv&&uv->u.
l.
prev->u.
l.
next==uv);男由returnuv;男甲}这个函数先在当前的畯異略畮畵異當畡畬链表中寻找是否已经转化过,如果有就复用之.
否则,构造一个新的畵異當畡畬畵略对象,并把它串到畯異略畮畵異當畡畬链表中.
当离开一个代码块时,这个代码块中定义的局部变量变得不可见.
界畵畡会调整数据栈指针,销毁掉这些变量.
若这些栈值被某些闭包以开放畵異當畡畬畵略的形式引用,就需要把它们关闭.
畬畵畡畆畣畬畯畳略实现了这一系列关闭操作:删掉已经无人引用的畵異當畡畬畵略、把数据从数据栈上复制到畕異畖畡畬结构中,并修正畕異畖畡畬中的指针當.
源代码电甮男町畬畦畵畮畣甮畣町畬畵畡畆畣畬畯畳略甸甲voidluaF_freeupval(lua_State*L,UpVal*uv){甸申if(uv->v!
=&uv->u.
value)/*isitopen*/甸甴unlinkupval(uv);/*removefromopenlist*/甸电luaM_free(L,uv);/*freeupvalue*/甸甶}甸男甸甸甸甹voidluaF_close(lua_State*L,StkIdlevel){甹田UpVal*uv;甹由global_State*g=G(L);甹甲while(L->openupval!
=NULL&&(uv=gco2uv(L->openupval))->v>=level){甹申GCObject*o=obj2gco(uv);电甮申闭包甴电甹甴lua_assert(!
isblack(o)&&uv->v!
=&uv->u.
value);甹电L->openupval=uv->next;/*removefrom'open'list*/甹甶if(isdead(g,o))甹男luaF_freeupval(L,uv);/*freeupvalue*/甹甸else{甹甹unlinkupval(uv);/*removeupvaluefrom'uvhead'list*/由田田setobj(L,&uv->u.
value,uv->v);/*movevaluetoupvalueslot*/由田由uv->v=&uv->u.
value;/*nowcurrentvalueliveshere*/由田甲gch(o)->next=g->allgc;/*linkupvalueinto'allgc'list*/由田申g->allgc=o;由田甴luaC_checkupvalcolor(g,uv);由田电}由田甶}由田男}第二种生成界畵畡闭包的方式是加载一段界畵畡代码.
每段界畵畡代码都会被编译成一个函数原型,但界畵畡的外部畁畐畉是不返回函数原型对象的,而是把这个函数原型转换为一个界畵畡闭包对象.
如果从源代码加载的话,不可能有用户构建出来的畵異當畡畬畵略的.
但是,任何一个代码块都至少有一个畵異當畡畬畵略:畅畎畖.
7如果用户试图畤畵畭異一个拥有多个畵異當畡畬畵略的界畵畡闭包,它会得到一个畵異當畡畬畵略数量不为一的函数原型的二进制数据块.
畵畮畤畵畭異这个数据块,就将生成多个畵異當畡畬畵略的闭包.
在畬畤畯甮畣中,我们能读到畦異畡畲畳略畲函数把加载进内存的函数原型和畵異當畡畬畵略绑定起来.
源代码电甮甸町畬畤畯甮畣町畦異畡畲畳略畲甶申甲staticvoidf_parser(lua_State*L,void*ud){甶申申inti;甶申甴Closure*cl;甶申电structSParser*p=cast(structSParser*,ud);甶申甶intc=zgetc(p->z);/*readfirstcharacter*/甶申男if(c==LUA_SIGNATURE[0]){甶申甸checkmode(L,p->mode,"binary");甶申甹cl=luaU_undump(L,p->z,&p->buff,p->name);甶甴田}甶甴由else{甶甴甲checkmode(L,p->mode,"text");甶甴申cl=luaY_parser(L,p->z,&p->buff,&p->dyd,p->name,c);甶甴甴}甶甴电lua_assert(cl->l.
nupvalues==cl->l.
p->sizeupvalues);甶甴甶for(i=0;il.
nupvalues;i+initializeupvalues*/甶甴男UpVal*up=luaF_newupval(L);甶甴甸cl->l.
upvals[i]=up;7ENV从Lua5.
2开始引入Lua语言,同时废弃了之前版本中环境这个概念.
Lua的核心不再区分全局表、环境表这些.
访问全局变量只是对ENV这张表访问的语法糖.
ENV必须被每一个Lua函数可见,所以被放在Lua代码块的第一个upvalue中.
甴甶第五章函数与闭包甶甴甹luaC_objbarrier(L,cl,up);甶电田}甶电由}这些畵異當畡畬畵略并不源于数据栈,所以是用畬畵畡畆畮略畷畵異當畡畬新构造出来的.
源代码电甮甹町畬畦畵畮畣甮畣町畬畵畡畆畮略畷畵異當畡畬申甹UpVal*luaF_newupval(lua_State*L){甴田UpVal*uv=&luaC_newobj(L,LUA_TUPVAL,sizeof(UpVal),NULL,0)->uv;甴由uv->v=&uv->u.
value;甴甲setnilvalue(uv->v);甴申returnuv;甴甴}5.
3.
2C闭包作为嵌入式语言,界畵畡必须有和畃程序方便交互的能力.
畃函数可以以闭包的形式方便的嵌入界畵畡.
和界畵畡闭包不同,在畃函数中不会去引用外层函数中的局部变量.
所以,畃闭包中的畵異當畡畬畵略天生就是关闭状态的.
界畵畡也就不需要用独立的畕異畖畡畬对象来引用它们.
对于畃闭包,畵異當畡畬畵略是直接存放在畃畃畬畯畳畵畲略结构中的.
源代码电甮由田町畬畯畢番略畣畴甮畨町畃畃畬畯畳畵畲略电由甶typedefstructCClosure{电由男ClosureHeader;电由甸lua_CFunctionf;电由甹TValueupvalue[1];/*listofupvalues*/电甲田}CClosure;畃闭包的构造流程也相对简单的多:源代码电甮由由町畬畦畵畮畣甮畣町畬畵畡畆畮略畷畃畣畬畯畳畵畲略甲申Closure*luaF_newCclosure(lua_State*L,intn){甲甴Closure*c=&luaC_newobj(L,LUA_TCCL,sizeCclosure(n),NULL,0)->cl;甲电c->c.
nupvalues=cast_byte(n);甲甶returnc;甲男}5.
3.
3轻量C函数当畃闭包一个畵異當畡畬畵略都没有时,可想而知,我们其实不必使用一个复杂的数据结构来表示它.
直接用畃函数指针即可,而且相同的畃函数是可以复用的,不必每次构造出来时都生成一个新的畃畃畬畯畳畵畲略对象.
这就是界畵畡电甮甲开始引入的轻量畃函数的概念.
这样的畃函数不需要由畇畃来管理其生命期,不必为之构建出闭包结构.
我们可以阅读畬畡異畩甮畣中的畬畵畡異畵畳畨畣畣畬畯畳畵畲略的实现来理解它的实现.
电甮申闭包甴男源代码电甮由甲町畬畡異畩甮畣町畬畵畡異畵畳畨畣畣畬畯畳畵畲略电电电LUA_APIvoidlua_pushcclosure(lua_State*L,lua_CFunctionfn,intn){电电甶lua_lock(L);电电男if(n==0){电电甸setfvalue(L->top,fn);电电甹}电甶田else{电甶由Closure*cl;电甶甲api_checknelems(L,n);电甶申api_check(L,nc.
f=fn;电甶男L->top-=n;电甶甸while(n--)电甶甹setobj2n(L,&cl->c.
upvalue[n],L->top+n);电男田setclCvalue(L,L->top,cl);电男由}电男甲api_incr_top(L);电男申lua_unlock(L);电男甴}对于轻量畃函数,直接把函数指针用宏畳略畴畦當畡畬畵略压入堆栈即可.
由#definesetfvalue(obj,x)\甲{TValue*io=(obj);val_(io).
f=(x);settt_(io,LUA_TLCF);}畳略畴畦當畡畬畵略给畔畖畡畬畵略设置了类型界畕畁畔界畃畆,它是界畕畁畔畆畕畎畃畔畉畏畎三个子类型中的一个,并未对外部畁畐畉公开,定义在畬畯畢番略畣畴甮畨中.
源代码电甮由申町畬畯畢番略畣畴甮畨町畬畵畡畔畆畕畎畃畔畉畏畎电田#defineLUA_TLCL(LUA_TFUNCTION|(0l_G)忽略畬畳畴畡畴略甮畨中涉及畧畣的复杂部分,我们可以先看一眼畬畵畡畓畴畡畴略的数据结构.
源代码甶甮由町畬畳畴畡畴略甮畨町畬畵畡畓畴畡畴略由电甴structlua_State{由电电CommonHeader;由电甶lu_bytestatus;由电男StkIdtop;/*firstfreeslotinthestack*/由电甸global_State*l_G;由电甹CallInfo*ci;/*callinfoforcurrentfunction*/由甶田constInstruction*oldpc;/*lastpctraced*/由甶由StkIdstack_last;/*lastfreeslotinthestack*/由甶甲StkIdstack;/*stackbase*/由甶申intstacksize;由甶甴unsignedshortnny;/*numberofnon-yieldablecallsinstack*/由甶电unsignedshortnCcalls;/*numberofnestedCcalls*/由甶甶lu_bytehookmask;由甶男lu_byteallowhook;由甶甸intbasehookcount;由甶甹inthookcount;由男田lua_Hookhook;由男由GCObject*openupval;/*listofopenupvaluesinthisstack*/由男甲GCObject*gclist;由男申structlua_longjmp*errorJmp;/*currenterrorrecoverpoint*/由男甴ptrdiff_terrfunc;/*currenterrorhandlingfunction(stackindex)*/由男电CallInfobase_ci;/*CallInfoforfirstlevel(CcallingLua)*/由男甶};这个数据结构是围绕程序如何执行来设计的,数据栈之外的数据储存并不体现在这个结构中.
在界畵畡源代码的其它部分,经常会用到畬畵畡畓畴畡畴略,但不是所有代码都需要了解结构的细节.
一般提到这类数据的地方,都用变量名界来指代它.
接下来,我们来分析畬畵畡畓畴畡畴略中重要的两个数据结构:数据栈和调用链.
6.
1.
1数据栈界畵畡中的数据可以这样分为两类:值类型和引用类型.
值类型可以被任意复制,而引用类型共享一份数据,由畇畃负责维护生命期.
界畵畡使用一个联合畵畮畩畯畮畖畡畬畵略来保存数据.
源代码甶甮甲町畬畯畢番略畣畴甮畨町畖畡畬畵略申甸甸unionValue{申甸甹GCObject*gc;/*collectableobjects*/申甹田void*p;/*lightuserdata*/甶甮由栈与调用信息电由申甹由intb;/*booleans*/申甹甲lua_CFunctionf;/*lightCfunctions*/申甹申numfield/*numbers*/申甹甴};从这里我们可以看到,引用类型用一个指针畇畃畏畢番略畣畴甪畧畣来间接引用,而其它值类型都直接保存在联合中.
为了区分联合中存放的数据类型,再额外绑定一个类型字段.
由#defineTValuefieldsValuevalue_;inttt_甲申typedefstructlua_TValueTValue;甴电structlua_TValue{甶TValuefields;男};这里,使用繁杂的宏定义,畔畖畡畬畵略甌略畬畤畳以及畮畵畭甌略畬畤是为了应用一个被称为畎畡畎畔畲畩畣畫的技巧.
5畬畵畡畓畴畡畴略的数据栈,就是一个畔畖畡畬畵略的数组.
代码中用畓畴畫畉畤类型来指代对畔畖畡畬畵略的引用.
由typedefTValue*StkId;/*indextostackelements*/在畬畳畴畡畴略甮畣中,我们可以读到对堆栈的初始化及释放的代码.
源代码甶甮申町畬畳畴畡畴略甮畣町畳畴畡畣畫由申申staticvoidstack_init(lua_State*L1,lua_State*L){由申甴inti;CallInfo*ci;由申电/*initializestackarray*/由申甶L1->stack=luaM_newvector(L,BASIC_STACK_SIZE,TValue);由申男L1->stacksize=BASIC_STACK_SIZE;由申甸for(i=0;istack+i);/*erasenewstack*/由甴田L1->top=L1->stack;由甴由L1->stack_last=L1->stack+L1->stacksize-EXTRA_STACK;由甴甲/*initializefirstci*/由甴申ci=&L1->base_ci;5在不修改Lua的默认配置的情况下,一个Lua数据需要8个字节来存放,即一个double的长度.
但是,类型信息需要额外的一个字节.
由于大多数平台需要数据对齐以保证数据访问的效率,所以Lua在默认实现中,使用int来保存数据类型信息.
这样,每个TValue的数据长度就需要12字节,增加了50%.
这看起来是一个不小的开销,所以,在Lua5.
2中,可以选择打开NaNTrick来把数据类型信息压缩进8字节的数据段.
所谓NaNTrick,就是指在现行的浮点数二进制标准IEEE754中,指数位全1时,表示这并不是一个数字.
它用来表示无穷大,以及数字除0的结果[9].
也就是说,一个浮点数是不是数字,只取决于它的指数部分,和尾数部分无关.
现实中的处理器,只会产生一种尾数全为0的NaN.
所有尾数不为0的NaN值,都可以看成是刻意构造出来的值.
换句话说,处理器只会产生值为0xf8000000000000的NaN,大于它的值都可以用作其它用途,且能和正常的浮点数区分开.
Double类型的尾数位有52位,而在32位平台上,仅需要32位即可表示Lua支持的除数字以外的所有类型(主要是指针),剩下的位置来保存类型信息足够了.
当Lua5.
2开启NaNTrick编译选项时,简单的把前24位设置为7FF7A5来标识非数字类型,留下8位储存类型信息.
对于目前Lua5.
2的实现,NaNTrick对于64位平台是没有意义的.
因为指针和double同样占用8字节的空间.
不过,64位平台上,地址指针有效位只有48位,理论上也是可以利用NaNTrick的,但这样会增加代码复杂度,且在64位平台上节约内存的意义相对较小,Lua5.
2的实现就没有这么做了.
倒是在LuaJIT2.
0中,对64位平台,同样采用了这个技巧.
电甲第六章协程及函数的执行由甴甴ci->next=ci->previous=NULL;由甴电ci->callstatus=0;由甴甶ci->func=L1->top;由甴男setnilvalue(L1->topfunction'entryforthis'ci'*/由甴甸ci->top=L1->top+LUA_MINSTACK;由甴甹L1->ci=ci;由电田}由电由由电甲由电申staticvoidfreestack(lua_State*L){由电甴if(L->stack==NULL)由电电return;/*stacknotcompletelybuiltyet*/由电甶L->ci=&L->base_ci;/*freetheentire'ci'list*/由电男luaE_freeCI(L);由电甸luaM_freearray(L,L->stack,L->stacksize);/*freestackarray*/由电甹}一开始,数据栈的空间很有限,只有甲倍的界畕畁畍畉畎畓畔畁畃畋的大小.
由#defineBASIC_STACK_SIZE(2*LUA_MINSTACK)界畕畁畍畉畎畓畔畁畃畋默认为甲田,配置在畬畵畡甮畨中.
任何一次界畵畡的畃函数调用,都只保证有这么大的空闲数据栈空间可用.
界畵畡供畃使用的栈相关畁畐畉都是不检查数据栈越界的,这是因为通常我们编写畃扩展都能把数据栈空间的使用控制在界畕畁畍畉畎畓畔畁畃畋以内,或是显式扩展.
对每次数据栈访问都强制做越界检查是非常低效的.
数据栈不够用的时候,可以扩展.
这种扩展是用畲略畡畬畬畯畣实现的,每次至少分配比原来大一倍的空间,并把旧的数据复制到新空间.
源代码甶甮甴町畬畤畯甮畣町畧畲畯畷畳畴畡畣畫由电男/*somespaceforerrorhandling*/由电甸#defineERRORSTACKSIZE(LUAI_MAXSTACK+200)由电甹由甶田由甶由voidluaD_reallocstack(lua_State*L,intnewsize){由甶甲TValue*oldstack=L->stack;由甶申intlim=L->stacksize;由甶甴lua_assert(newsizestack_last-L->stack==L->stacksize-EXTRA_STACK);由甶甶luaM_reallocvector(L,L->stack,L->stacksize,newsize,TValue);由甶男for(;limstack+lim);/*erasenewsegment*/由甶甹L->stacksize=newsize;甶甮由栈与调用信息电申由男田L->stack_last=L->stack+newsize-EXTRA_STACK;由男由correctstack(L,oldstack);由男甲}由男申由男甴由男电voidluaD_growstack(lua_State*L,intn){由男甶intsize=L->stacksize;由男男if(size>LUAI_MAXSTACK)/*errorafterextrasize*/由男甸luaD_throw(L,LUA_ERRERR);由男甹else{由甸田intneeded=cast_int(L->top-L->stack)+n+EXTRA_STACK;由甸由intnewsize=2*size;由甸甲if(newsize>LUAI_MAXSTACK)newsize=LUAI_MAXSTACK;由甸申if(newsizeLUAI_MAXSTACK){/*stackoverflow*/由甸电luaD_reallocstack(L,ERRORSTACKSIZE);由甸甶luaG_runerror(L,"stackoverflow");由甸男}由甸甸else由甸甹luaD_reallocstack(L,newsize);由甹田}由甹由}数据栈扩展的过程,伴随着数据拷贝.
这些数据都是可以直接值复制的,所以不需要在扩展之后修正其中的指针.
但,有些外部结构对数据栈的引用需要修正为正确的新地址.
这些需要修正的位置包括畵異當畡畬畵略以及执行栈对数据栈的引用.
这个过程由畣畯畲畲略畣畴畳畴畡畣畫函数实现.
源代码甶甮电町畬畤畯甮畣町畣畯畲畲略畣畴畳畴畡畣畫由甴甲staticvoidcorrectstack(lua_State*L,TValue*oldstack){由甴申CallInfo*ci;由甴甴GCObject*up;由甴电L->top=(L->top-oldstack)+L->stack;由甴甶for(up=L->openupval;up!
=NULL;up=up->gch.
next)由甴男gco2uv(up)->v=(gco2uv(up)->v-oldstack)+L->stack;由甴甸for(ci=L->ci;ci!
=NULL;ci=ci->previous){由甴甹ci->top=(ci->top-oldstack)+L->stack;由电田ci->func=(ci->func-oldstack)+L->stack;由电由if(isLua(ci))由电甲ci->u.
l.
base=(ci->u.
l.
base-oldstack)+L->stack;由电申}由电甴}电甴第六章协程及函数的执行6.
1.
2调用栈界畵畡把调用栈和数据栈分开保存.
调用栈放在一个叫做畃畡畬畬畉畮畦畯的结构中,以双向链表的形式储存在线程对象里.
源代码甶甮甶町畬畳畴畡畴略甮畨町畃畡畬畬畉畮畦畯甶甹typedefstructCallInfo{男田StkIdfunc;/*functionindexinthestack*/男由StkIdtop;/*topforthisfunction*/男甲structCallInfo*previous,*next;/*dynamiccalllink*/男申shortnresults;/*expectednumberofresultsfromthisfunction*/男甴lu_bytecallstatus;男电ptrdiff_textra;男甶union{男男struct{/*onlyforLuafunctions*/男甸StkIdbase;/*baseforthisfunction*/男甹constInstruction*savedpc;甸田}l;甸由struct{/*onlyforCfunctions*/甸甲intctx;/*contextinfo.
incaseofyields*/甸申lua_CFunctionk;/*continuationincaseofyields*/甸甴ptrdiff_told_errfunc;甸电lu_byteold_allowhook;甸甶lu_bytestatus;甸男}c;甸甸}u;甸甹}CallInfo;畃畡畬畬畉畮畦畯保存着正在调用的函数的运行状态.
状态标示存放在畣畡畬畬畳畴畡畴畵畳中.
部分数据和函数的类型有关,以联合形式存放.
畃函数与界畵畡函数的结构不完全相同.
畣畡畬畬畳畴畡畴畵畳中保存了一位标志用来区分是畃函数还是界畵畡函数.
6由#defineisLua(ci)((ci)->callstatus&CIST_LUA)正在调用的函数一定存在于数据栈上,在畃畡畬畬畉畮畦畯结构中,畦畵畮畣指向正在执行的函数在数据栈上的位置.
需要记录这个信息,是因为如果当前是一个界畵畡函数,且传入的参数个数不定的时候,需要用这个位置和当前数据栈底的位置相减,获得不定参数的准确数量7.
同时,畦畵畮畣还可以帮助我们调试嵌入式界畵畡代码.
在用畧畤畢这样的调试器调试代码时,可以方便的查看畃中的调用栈信息,但一旦嵌入界畵畡,我们很难理解运行过程中的界畵畡代码的调用栈.
不理解界畵畡的内部结构,就可能面对一个简单的畬畵畡畓畴畡畴略界变量束手无策.
6在Lua5.
1中,没有callstatus这个域.
而是通过访问func引用的函数对象来了解函数是C函数还是Lua函数.
有callstatus后,目前5.
2的版本少一次间接内存访问,要略微高效一些.
7参见:7.
2.
7节.
甶甮由栈与调用信息电电实际上,遍历界中的畣畩域指向的畃畡畬畬畉畮畦畯链表可以获得完整的界畵畡调用链.
而每一级的畃畡畬畬畉畮畦畯中,都可以进一步的通过畦畵畮畣域取得所在函数的更详细信息.
当畦畵畮畣为一个界畵畡函数时,根据它的函数原型8可以获得源文件名、行号等诸多调试信息.
下面展示的是一个粗略的畧畤畢脚本,可以利用当前栈上的界分析出界畵畡的调用栈.
9由definebtlua甲set$p=L.
ci申while($p!
=0)甴set$tt=($p.
func.
tt_&0x3f)电if($tt==0x06)甶set$proto=$p.
func.
value_.
gc.
cl.
l.
p男set$filename=(char*)(&($proto.
source.
tsv)+1)甸set$lineno=$proto.
lineinfo[$p.
u.
l.
savedpc-$proto.
code-1]甹printf"0x%xLUAFUNCTION:%4d%s\n",$p,$lineno,$filename由田由由set$p=$p.
previous由甲loop_continue由申end由甴由电if($tt==0x16)由甶printf"0x%xLIGHTCFUNCTION",$p由男output$p.
func.
value_.
f由甸printf"\n"由甹甲田set$p=$p.
previous甲由loop_continue甲甲end甲申甲甴if($tt==0x26)甲电printf"0x%xCFUNCTION",$p甲甶output$p.
func.
value_.
gc.
cl.
c.
f甲男printf"\n"甲甸甲甹set$p=$p.
previous申田loop_continue申由end申甲申申printf"0x%xLUABASE\n",$p申甴set$p=$p.
previous申电end8在gdb中,可以通过func.
value.
gc.
cl.
l.
p获得等价于C代码中clLvalue(func).
p这个宏的功能.
9这段gdb脚本是由我的同事阿楠在工作中创作,用于我们一个C与Lua5.
2混合编程项目的调试.
功能虽不算太完善,但基本够用.
一旦了解其原理,很容易在其它调试器下实现同样的功能,或编写出试用于Lua其它版本的实现.
电甶第六章协程及函数的执行申甶end畃畡畬畬畉畮畦畯是一个标准的双向链表结构,不直接被畇畃模块管理.
这个双向链表表达的是一个逻辑上的栈,在运行过程中,并不是每次调入更深层次的函数,就立刻构造出一个畃畡畬畬畉畮畦畯节点.
整个畃畡畬畬畉畮畦畯链表会在运行中被反复复用.
直到畇畃的时候才清理那些比当前调用层次更深的无用节点.
畬畳畴畡畴略甮畣中有畬畵畡畅略畸畴略畮畤畃畉的实现:源代码甶甮男町畬畳畴畡畴略甮畣町畬畵畡畅略畸畴略畮畤畃畉由由甲CallInfo*luaE_extendCI(lua_State*L){由由申CallInfo*ci=luaM_new(L,CallInfo);由由甴lua_assert(L->ci->next==NULL);由由电L->ci->next=ci;由由甶ci->previous=L->ci;由由男ci->next=NULL;由由甸returnci;由由甹}但具体执行部分并不直接调用这个畁畐畉而是使用另一个宏:源代码甶甮甸町畬畤畯甮畣町畮略畸畴畣畩甲甸甹#definenext_ci(L)(L->ci=(L->ci->nextL->ci->next:luaE_extendCI(L)))也就是说,调用者只需要把畃畡畬畬畉畮畦畯链表当成一个无限长的堆栈使用即可.
当调用层次返回,之前分配的节点可以被后续调用行为复用.
在畇畃的时候只需要调用畬畵畡畅畦畲略略畃畉就可以释放过长的链表.
源代码甶甮甹町畬畳畴畡畴略甮畣町畬畵畡畅畦畲略略畃畉由甲甲voidluaE_freeCI(lua_State*L){由甲申CallInfo*ci=L->ci;由甲甴CallInfo*next=ci->next;由甲电ci->next=NULL;由甲甶while((ci=next)!
=NULL){由甲男next=ci->next;由甲甸luaM_free(L,ci);由甲甹}由申田}6.
1.
3线程把数据栈和调用栈合起来就构成了界畵畡中的线程.
在同一个界畵畡虚拟机中的不同线程因为共享了畧畬畯畢畡畬畓畴畡畴略而很难做到真正意义上的并发.
它也绝非操作系统意义上的线程,但在行为上很相似.
用户可以畲略畳畵畭略一个线程,线程可以被畹畩略畬畤打断.
界畵畡的执行过程就是围绕线程进行的.
我们从畬畵畡畮略畷畴畨畲略畡畤阅读起,可以更好的理解它的数据结构.
甶甮由栈与调用信息电男源代码甶甮由田町畬畳畴畡畴略甮畣町畬畵畡畮略畷畴畨畲略畡畤甲申申LUA_APIlua_State*lua_newthread(lua_State*L){甲申甴lua_State*L1;甲申电lua_lock(L);甲申甶luaC_checkGC(L);甲申男L1=&luaC_newobj(L,LUA_TTHREAD,sizeof(LX),NULL,offsetof(LX,l))->th;甲申甸setthvalue(L,L->top,L1);甲申甹api_incr_top(L);甲甴田preinit_state(L1,G(L));甲甴由L1->hookmask=L->hookmask;甲甴甲L1->basehookcount=L->basehookcount;甲甴申L1->hook=L->hook;甲甴甴resethookcount(L1);甲甴电luai_userstatethread(L,L1);甲甴甶stack_init(L1,L);/*initstack*/甲甴男lua_unlock(L);甲甴甸returnL1;甲甴甹}这里我们能发现,内存中的线程结构并非畬畵畡畓畴畡畴略,而是一个叫界畘的东西.
下面来看一看界畘的定义:源代码甶甮由由町畬畳畴畡畴略甮畣町界畘电甹typedefstructLX{甶田#ifdefined(LUAI_EXTRASPACE)甶由charbuff[LUAI_EXTRASPACE];甶甲#endif甶申lua_Statel;甶甴}LX;在畬畵畡畓畴畡畴略之前留出了大小为界畕畁畉畅畘畔畒畁畓畐畁畃畅字节的空间.
面对外部用户操作的指针是界而不是界畘,但界所占据的内存块的前面却是有所保留的.
这是一个有趣的技巧.
用户可以在拿到界指针后向前移动指针,取得一些畅畘畔畒畁畓畐畁畃畅中额外的数据.
把这些数据放在前面而不是畬畵畡畓畴畡畴略结构的后面避免了向用户暴露结构的大小.
这里,界畕畁畉畅畘畔畒畁畓畐畁畃畅是通过编译配置的,默认为田.
开启畅畘畔畒畁畓畐畁畃畅后,还需要定义下列宏来配合工作.
由luai_userstateopen(L)甲uai_userstateclose(L)申luai_userstatethread(L,L1)甴luai_userstatefree(L,L1)电luai_userstateresume(L,n)甶luai_userstateyield(L,n)电甸第六章协程及函数的执行给界附加一些用户自定义信息在追求性能的环境很有意义.
可以在为界畵畡编写的畃模块中,直接偏移界指针来获取一些附加信息.
这比去读取界中的注册表要高效的多.
另一方面,在多线程环境下,访问注册表本身会改变界的状态,是线程不安全的.
6.
2线程的执行与中断界畵畡的程序运行是以线程为单位的.
每个界畵畡线程可以独立运行直到自行中断,把中断的信息留在状态机中.
每条线程的执行互不干扰,可以独立延续之前中断的执行过程.
界畵畡的线程和系统线程无关,所以不会为每条界畵畡线程创建独立的系统堆栈,而是利用自己维护的线程栈,内存开销也就远小于系统线程.
但界畵畡又是一门嵌入式语言,和畃语言混合编程是一种常态.
一旦界畵畡调用的畃库中企图中断线程,延续它就是一个巨大的难题10.
界畵畡电甮甲通过一些巧妙的设计绕过了这个问题.
6.
2.
1异常处理如果界畵畡被实现为一个纯粹的运行在字节码虚拟机上的语言11,只要不出虚拟机,可以很容易的实现自己的线程和异常处理.
事实上,界畵畡函数调用层次上只要没有畃函数,是不会在畃层面的调用栈上深入下去的12.
但当界畵畡函数调用了畃函数,而这个畃函数中又进一步的回调了界畵畡函数,这个问题就复杂的多.
考虑一下畬畵畡中的異畡畩畲畳函数,就是一个典型的畃扩展函数,却又回调了界畵畡函数.
界畵畡底层把异常和线程中断用同一种机制来处理,也就是使用了畃语言标准的畬畯畮畧番畭異机制来解决这个问题.
为了兼顾畃甫甫开发,在畃甫甫环境下,可以把它配置为使用畃甫甫的畴畲畹甯畣畡畴畣畨机制.
13这些是通过宏定义来切换的.
源代码甶甮由甲町畬畤畯甮畣町界畕畁畉畔畈畒畏畗电由#ifdefined(__cplusplus)&&!
defined(LUA_USE_LONGJMP)电甲/*C++exceptions*/电申#defineLUAI_THROW(L,c)throw(c)电甴#defineLUAI_TRY(L,c,a)\电电try{a}catch(.
.
.
){if((c)->status==0)(c)->status=-1;}电甶#defineluai_jmpbufint/*dummyvariable*/电男电甸#elifdefined(LUA_USE_ULONGJMP)电甹/*inUnix,try_longjmp/_setjmp(moreefficient)*/甶田#defineLUAI_THROW(L,c)_longjmp((c)->b,1)甶由#defineLUAI_TRY(L,c,a)if(_setjmp((c)->b)==0){a}甶甲#defineluai_jmpbufjmp_buf甶申甶甴#else10这里涉及C调用层次上的系统堆栈保存和恢复问题.
11Java就是这样,不用太考虑和原生代码的交互问题.
12一次纯粹的Lua函数调用,不会引起一次虚拟机上的C函数调用.
13用C++开发的Lua扩展库,并不一定要链接到C++编译的Lua虚拟机才能正常工作.
如果你的库不必回调Lua函数,就不会有这个烦恼.
使用try/catch不会比longjmp更安全.
如果你需要在C++扩展库中调用luaerror或luayield中断C函数的运行,那么考虑到可能发生的内存泄露问题,就有必要换成try/catch的版本.
甶甮甲线程的执行与中断电甹甶电/*defaulthandlingwithlongjumps*/甶甶#defineLUAI_THROW(L,c)longjmp((c)->b,1)甶男#defineLUAI_TRY(L,c,a)if(setjmp((c)->b)==0){a}甶甸#defineluai_jmpbufjmp_buf甶甹男田#endif每条线程界中保存了当前的畬畯畮畧番畭異返回点:略畲畲畯畲畊畭異,其结构定义为畳畴畲畵畣畴畬畵畡畬畯畮畧番畭異.
这是一条链表,每次运行一段受保护的界畵畡代码,都会生成一个新的错误返回点,链到这条链表上.
下面展示的是畬畵畡畬畯畮畧番畭異的结构:源代码甶甮由申町畬畤畯甮畣町畬畵畡畬畯畮畧番畭異男男structlua_longjmp{男甸structlua_longjmp*previous;男甹luai_jmpbufb;甸田volatileintstatus;/*errorcode*/甸由};设置畬畯畮畧番畭異返回点是由畬畵畡畄畲畡畷畲畵畮異畲畯畴略畣畴略畤完成的.
源代码甶甮由甴町畬畤畯甮畣町畬畵畡畄畲畡畷畲畵畮異畲畯畴略畣畴略畤由甲电intluaD_rawrunprotected(lua_State*L,Pfuncf,void*ud){由甲甶unsignedshortoldnCcalls=L->nCcalls;由甲男structlua_longjmplj;由甲甸lj.
status=LUA_OK;由甲甹lj.
previous=L->errorJmp;/*chainnewerrorhandler*/由申田L->errorJmp=&lj;由申由LUAI_TRY(L,&lj,由申甲(*f)(L,ud);由申申);由申甴L->errorJmp=lj.
previous;/*restoreolderrorhandler*/由申电L->nCcalls=oldnCcalls;由申甶returnlj.
status;由申男}这段代码很容易理解:设置新的番畭異畢畵畦,串到链表上,调用函数.
调用完成后恢复进入时的状态.
如果想回直接返回到最近的错误恢复点,只需要调用畬畯畮畧番畭異.
界畵畡用了一个内部畁畐畉畬畵畡畄畴畨畲畯畷封装了这个过程.
源代码甶甮由电町畬畤畯甮畣町畬畵畡畄畴畨畲畯畷由田申l_noretluaD_throw(lua_State*L,interrcode){由田甴if(L->errorJmp){/*threadhasanerrorhandler*/由田电L->errorJmp->status=errcode;/*setstatus*/由田甶LUAI_THROW(L,L->errorJmp);/*jumptoit*/甶田第六章协程及函数的执行由田男}由田甸else{/*threadhasnoerrorhandler*/由田甹L->status=cast_byte(errcode);/*markitasdead*/由由田if(G(L)->mainthread->errorJmp){/*mainthreadhasahandler*/由由由setobjs2s(L,G(L)->mainthread->top++,L->top-1);/*copyerrorobj.
*/由由甲luaD_throw(G(L)->mainthread,errcode);/*re-throwinmainthread*/由由申}由由甴else{/*nohandleratall;abort*/由由电if(G(L)->panic){/*panicfunction*/由由甶lua_unlock(L);由由男G(L)->panic(L);/*callit(lastchancetojumpout)*/由由甸}由由甹abort();由甲田}由甲由}由甲甲}考虑到新构造的线程可能在不受保护的情况下运行14.
这时的任何错误都必须被捕获,不能让程序崩溃.
这种情况合理的处理方式就是把正在运行的线程标记为死线程,并且在主线程中抛出异常.
6.
2.
2函数调用函数调用分为受保护调用和不受保护的调用.
受保护的函数调用可以看成是一个畃层面意义上完整的过程.
在界畵畡代码中,異畣畡畬畬是用函数而不是语言机制完成的.
受保护的函数调用一定在畃层面进出了一次调用栈.
它使用独立的一个内部畁畐畉畬畵畡畄異畣畡畬畬来实现.
公开畁畐畉仅需要对它做一些封装即可.
15源代码甶甮由甶町畬畤畯甮畣町畬畵畡畄異畣畡畬畬电甸男intluaD_pcall(lua_State*L,Pfuncfunc,void*u,电甸甸ptrdiff_told_top,ptrdiff_tef){电甸甹intstatus;电甹田CallInfo*old_ci=L->ci;电甹由lu_byteold_allowhooks=L->allowhook;电甹甲unsignedshortold_nny=L->nny;电甹申ptrdiff_told_errfunc=L->errfunc;电甹甴L->errfunc=ef;电甹电status=luaD_rawrunprotected(L,func,u);电甹甶if(status!
=LUA_OK){/*anerroroccurred*/14这一般是错误的使用Lua导致的.
正确使用Lua线程的方式是把主函数压入新线程,然后调用luaresume启动它.
而luaresume可以保护线程的主函数运行.
15luaDpcall主要被用来实现外部APIluapcallk.
在lua虚拟机的实现中并不需要使用这个API,这是因为pcall是作用标准库函数引入lua的,并非一个语言特性.
但也并非lua核心代码不需要luaDpcall,在GC过程中释放对象,调用对象的GC元方法时需要使用luaDpcall.
因为GC的发生时刻不可预知,不能让GC流程干扰了正常代码的运行,必须把对GC元方法的调用保护起来.
甶甮甲线程的执行与中断甶由电甹男StkIdoldtop=restorestack(L,old_top);电甹甸luaF_close(L,oldtop);/*closepossiblependingclosures*/电甹甹seterrorobj(L,status,oldtop);甶田田L->ci=old_ci;甶田由L->allowhook=old_allowhooks;甶田甲L->nny=old_nny;甶田申luaD_shrinkstack(L);甶田甴}甶田电L->errfunc=old_errfunc;甶田甶returnstatus;甶田男}从这段代码我们可以看到異畣畡畬畬的处理模式:用畃层面的堆栈来保存和恢复状态.
畣畩、畡畬畬畯畷畨畯畯畫畳、畮畮畹16、略畲畲畦畵畮畣都保存在畬畵畡畄異畣畡畬畬的畃堆栈上,一旦畬畵畡畄畲畡畷畲畵畮異畲畯畴略畣畴略畤就可以正确恢复.
畬畵畡畄畲畡畷畲畵畮異畲畯畴略畣畴略畤没有正确返回时,需要根据畯畬畤畴畯異找到堆栈上刚才调用的函数,给它做收尾工作17.
因为畬畵畡畄畲畡畷畲畵畮異畲畯畴略畣畴略畤调用的是一个函数对象,而不是数据栈上的索引,这就需要额外的变量来定位了.
这里使用畲略畳畴畯畲略畳畴畡畣畫这个宏来定位栈上的地址,是因为数据栈的内存地址是会随着数据栈的大小而变化.
保存地址是不可能的,而应该记住一个相对量.
畳畡當略畳畴畡畣畫和畲略畳畴畯畲略畳畴畡畣畫两个宏就是做这个工作的.
源代码甶甮由男町畬畤畯甮畨町畲略畳畴畯畲略畳畴畡畣畫甲甲#definesavestack(L,p)((char*)(p)-(char*)L->stack)甲申#definerestorestack(L,n)((TValue*)((char*)L->stack+(n)))一般的界畵畡层面的函数调用并不对应一个畃层面上函数调用行为.
对于界畵畡函数而言,应该看成是生成新的畃畡畬畬畉畮畦畯,修正数据栈,然后把字节码的执行位置跳转到被调用的函数开头.
而界畵畡函数的畲略畴畵畲畮操作则做了相反的操作,恢复数据栈,弹出畃畡畬畬畉畮畦畯,修改字节码的执行位置,恢复到原有的执行序列上.
理解了这一点,就能明白,在底层畁畐畉中,为何分为畬畵畡畄異畲略畣畡畬畬和畬畵畡畄異畯畳畣畡畬畬两个了.
畬畵畡畄異畲略畣畡畬畬执行的是函数调用部分的工作,而畬畵畡畄異畯畳畣畡畬畬做的是函数返回的工作.
从畃层面看,层层函数调用的过程并不是递归的.
对于畃类型的函数调用,整个函数调用是完整的,不需要等待后续再调用畬畵畡畄異畯畳畣畡畬畬,所以畬畵畡畄異畲略畣畡畬畬可以代其完成,并返回由;而界畵畡函数,执行完畬畵畡畄異畲略畣畡畬畬后,只是切换了畬畵畡畓畴畡畴略的执行状态,被调用的函数的字节码尚未运行,畬畵畡畄異畲略畣畡畬畬返回田.
待到虚拟机执行到对应的畲略畴畵畲畮指令时,才会去调用畬畵畡畄異畯畳畣畡畬畬完成整次调用.
我们先看看畬畵畡畄異畲略畣畡畬畬的源码:源代码甶甮由甸町畬畤畯甮畣町畬畵畡畄異畲略畣畡畬畬甲甹电intluaD_precall(lua_State*L,StkIdfunc,intnresults){甲甹甶lua_CFunctionf;甲甹男CallInfo*ci;甲甹甸intn;/*numberofarguments(Lua)orreturns(C)*/16nny的全称是numberofnon-yieldablecalls.
C语言本身无法提供延续点的支持,所以Lua也无法让所有函数都是yieldable的.
当一级函数处于non-yieldable状态时,更深的层次都无法yieldable.
这个变量用于监督这个状态,在错误发生时报告.
每级C调用是否允许yield取决于是否有设置C延续点,或是Lua内核实现时认为这次调用在发生yield时无法正确处理.
这些都由luaDcall的最后一个参数来制定.
17调用luaFclose涉及upvalue的gc流程,参见GC的章节.
甶甲第六章协程及函数的执行甲甹甹ptrdiff_tfuncr=savestack(L,func);申田田switch(ttype(func)){申田由caseLUA_TLCF:/*lightCfunction*/申田甲f=fvalue(func);申田申gotoCfunc;申田甴caseLUA_TCCL:{/*Cclosure*/申田电f=clCvalue(func)->f;申田甶Cfunc:申田男luaD_checkstack(L,LUA_MINSTACK);/*ensureminimumstacksize*/申田甸ci=next_ci(L);/*now'enter'newfunction*/申田甹ci->nresults=nresults;申由田ci->func=restorestack(L,funcr);申由由ci->top=L->top+LUA_MINSTACK;申由甲lua_assert(ci->topstack_last);申由申ci->callstatus=0;申由甴luaC_checkGC(L);/*stackgrowusesmemory*/申由电if(L->hookmask&LUA_MASKCALL)申由甶luaD_hook(L,LUA_HOOKCALL,-1);申由男lua_unlock(L);申由甸n=(*f)(L);/*dotheactualcall*/申由甹lua_lock(L);申甲田api_checknelems(L,n);申甲由luaD_poscall(L,L->top-n);申甲甲return1;申甲申}申甲甴caseLUA_TLCL:{/*Luafunction:prepareitscall*/申甲电StkIdbase;申甲甶Proto*p=clLvalue(func)->p;申甲男luaD_checkstack(L,p->maxstacksize);申甲甸func=restorestack(L,funcr);申甲甹n=cast_int(L->top-func)-1;/*numberofrealarguments*/申申田for(;nnumparams;n++)申申由setnilvalue(L->top++);/*completemissingarguments*/申申甲base=(!
p->is_vararg)func+1:adjust_varargs(L,p,n);申申申ci=next_ci(L);/*now'enter'newfunction*/申申甴ci->nresults=nresults;申申电ci->func=func;申申甶ci->u.
l.
base=base;申申男ci->top=base+p->maxstacksize;申申甸lua_assert(ci->topstack_last);申申甹ci->u.
l.
savedpc=p->code;/*startingpoint*/甶甮甲线程的执行与中断甶申申甴田ci->callstatus=CIST_LUA;申甴由L->top=ci->top;申甴甲luaC_checkGC(L);/*stackgrowusesmemory*/申甴申if(L->hookmask&LUA_MASKCALL)申甴甴callhook(L,ci);申甴电return0;申甴甶}申甴男default:{/*notafunction*/申甴甸func=tryfuncTM(L,func);/*retrywith'function'tagmethod*/申甴甹returnluaD_precall(L,func,nresults);/*nowitmustbeafunction*/申电田}申电由}申电甲}界畩畧畨畴畃畦畵畮畣畴畩畯畮和畃畣畬畯畳畵畲略仅仅是在储存上有所不同,处理逻辑是一致的:压入新的畃畡畬畬畉畮畦畯,把数据栈栈顶设置好.
调用畃函数,然后畬畵畡畄異畯畳畣畡畬畬.
界畵畡函数要复杂一些:先通过传入函数对象在数据栈上的位置和栈顶差,计算出数据栈上的调用参数个数畮.
如果界畵畡函数对输入参数个数有明确的最小要求,这点可以通过查询函数原型的畮畵畭異畡畲畡畭畳字段获知;若栈上提供的参数数量不足,就需要把不足的部分补为畮畩畬.
当调用函数需要可变参数的时候,还需要进一步处理:源代码甶甮由甹町畬畤畯甮畣町畡畤番畵畳畴當畡畲畡畲畧畳甲电男staticStkIdadjust_varargs(lua_State*L,Proto*p,intactual){甲电甸inti;甲电甹intnfixargs=p->numparams;甲甶田StkIdbase,fixed;甲甶由lua_assert(actual>=nfixargs);甲甶甲/*movefixedparameterstofinalposition*/甲甶申fixed=L->top-actual;/*firstfixedargument*/甲甶甴base=L->top;/*finalpositionoffirstargument*/甲甶电for(i=0;itop++,fixed+i);甲甶男setnilvalue(fixed+i);甲甶甸}甲甶甹returnbase;甲男田}变长参数表这个概念,只在界畵畡函数中出现.
当一个函数接收变长参数时,这部分的参数是放在上一级数据栈帧尾部的.
畡畤番畵畳畴當畡畲畡畲畧畳将需要个固定参数复制到被调用的函数的新一级数据栈帧上,而变长参数留在原地.
接下来,要构造出新的一层调用栈畃畡畬畬畉畮畦畯.
这个结构需要初始化字节码执行指针畳畡當略畤異畣,将其指向畬畵畡函数对象中的字节指令区.
界畵畡函数整体所需要的栈空间是在生成字节码时就已知的,所以可以用甶甴第六章协程及函数的执行畬畵畡畄畣畨略畣畫畳畴畡畣畫一次性分配好.
畃畡畬畬畉畮畦畯中的栈顶可以直接调整到位18.
畃畡畬畬畉畮畦畯中的返回参数个数,所引用的函数对象一一初始化完毕,最后初始化线程运行状态畣畡畬畬畳畴畡畴畵畳,标记上畃畉畓畔界畕畁就够了.
真正如何运行界畵畡函数,是由调用畬畵畡畄異畲略畣畡畬畬者决定的.
有些对象是通过元表驱动函数调用的行为的,这时需要通过畴畲畹畦畵畮畣畔畍函数取得真正的调用函数.
19源代码甶甮甲田町畬畤畯甮畣町畴畲畹畦畵畮畣畔畍甲男申staticStkIdtryfuncTM(lua_State*L,StkIdfunc){甲男甴constTValue*tm=luaT_gettmbyobj(L,func,TM_CALL);甲男电StkIdp;甲男甶ptrdiff_tfuncr=savestack(L,func);甲男男if(!
ttisfunction(tm))甲男甸luaG_typeerror(L,func,"call");甲男甹/*Openaholeinsidethestackat'func'*/甲甸田for(p=L->top;p>func;p--)setobjs2s(L,p,p-1);甲甸由incr_top(L);甲甸甲func=restorestack(L,funcr);/*previouscallmaychangestack*/甲甸申setobj2s(L,func,tm);/*tagmethodisthenewfunctiontobecalled*/甲甸甴returnfunc;甲甸电}根据畬畵畡的定义,通过元方法进行的函数调用和原生的函数调用有所区别.
通过元方法进行的函数调用,会将对象自身作为第一个参数传入.
这就需要移动数据栈,把对象插到第一个参数的位置.
这个过程在源码中有清晰的展示.
畬畵畡畄異畯畳畣畡畬畬做的工作也很简单,主要是数据栈的调整工作.
源代码甶甮甲由町畬畤畯甮畣町畬畵畡畄異畯畳畣畡畬畬申电电intluaD_poscall(lua_State*L,StkIdfirstResult){申电甶StkIdres;申电男intwanted,i;申电甸CallInfo*ci=L->ci;申电甹if(L->hookmask&(LUA_MASKRET|LUA_MASKLINE)){申甶田if(L->hookmask&LUA_MASKRET){申甶由ptrdiff_tfr=savestack(L,firstResult);/*hookmaychangestack*/申甶甲luaD_hook(L,LUA_HOOKRET,-1);申甶申firstResult=restorestack(L,fr);申甶甴}申甶电L->oldpc=ci->previous->u.
l.
savedpc;/*'oldpc'forcallerfunction*/申甶甶}18Lua5以后的虚拟机采用寄存器式设计,把栈空间当成若干个寄存器使用,所以在虚拟机运行一个代码块的过程中,几乎没有修改栈顶指针的操作.
19关于元表查询luaTgettmbyobj的部分,参见4.
4节.
甶甮甲线程的执行与中断甶电申甶男res=ci->func;/*res==finalpositionof1stresult*/申甶甸wanted=ci->nresults;申甶甹L->ci=ci=ci->previous;/*backtocaller*/申男田/*moveresultstocorrectplace*/申男由for(i=wanted;i!
=0&&firstResulttop;i--)申男甲setobjs2s(L,res++,firstResult++);申男申while(i-->0)申男甴setnilvalue(res++);申男电L->top=res;申男甶return(wanted-LUA_MULTRET);/*0iffwanted==LUA_MULTRET*/申男男}根据畬畵畡畄異畲略畣畡畬畬设置在畃畡畬畬畉畮畦畯里的返回参数的个数畮畲略畳畵畬畴畳,以及数据栈上在这次函数调用中实际新增的数据个数,需要对数据栈做一次调整.
多余的抛弃,不足的补为畮畩畬.
读完这些,再来看畬畵畡畄畣畡畬畬就很清晰了.
畬畵畡畄畣畡畬畬主要用来实现外部畁畐畉畬畵畡畣畡畬畬畫.
它调用完畬畵畡畄異畲略畣畡畬畬后,接着调用畬畵畡畖略畸略畣畵畴略完成对函数本身的字节码执行.
畬畵畡畄畣畡畬畬的最后一个参数用来标示这次调用是否可以在其中挂起.
因为在畬畵畡虚拟机执行期间,有许多情况都会引起新的一层畃层面的函数调用.
界畵畡线程并不拥有独立的畃堆栈,所以对于发生在畃函数内部的线程挂起操作,不是所有的情况都正确处理的.
是否接受畹畩略畬畤操作,只有调用畬畵畡畄畣畡畬畬才清楚.
源代码甶甮甲甲町畬畤畯甮畣町畬畵畡畄畣畡畬畬申甸甶voidluaD_call(lua_State*L,StkIdfunc,intnResults,intallowyield){申甸男if(++L->nCcalls>=LUAI_MAXCCALLS){申甸甸if(L->nCcalls==LUAI_MAXCCALLS)申甸甹luaG_runerror(L,"Cstackoverflow");申甹田elseif(L->nCcalls>=(LUAI_MAXCCALLS+(LUAI_MAXCCALLS>>3)))申甹由luaD_throw(L,LUA_ERRERR);/*errorwhilehandingstackerror*/申甹甲}申甹申if(!
allowyield)L->nny++;申甹甴if(!
luaD_precall(L,func,nResults))/*isaLuafunction*/申甹电luaV_execute(L);/*callit*/申甹甶if(!
allowyield)L->nny--;申甹男L->nCcalls--;申甹甸}如前面所述,畬畵畡虚拟机在解析字节码执行的过程中,对界畵畡函数的调用并不直接使用畬畵畡畄畣畡畬畬.
它不会产生畃层面的函数调用行为,就可以尽量不引起畃函数中挂起线程的问题.
但在某些情况上的处理,也这么做的话会让虚拟机的实现变得相当复杂.
这些情况包括畦畯畲语句引起的函数调用以及触发元方法引起的函数调用.
界畵畡利用畬畵畡畄畣畡畬畬,可以简化实现.
甶甶第六章协程及函数的执行6.
2.
3钩子界畵畡可以为每个线程设置一个钩子函数,用于调试、统计和其它一些特殊用法.
20钩子函数是一个畃函数,用内部畁畐畉畬畵畡畄畨畯畯畫封装起来.
源代码甶甮甲申町畬畤畯甮畣町畬畵畡畄畨畯畯畫甲由男voidluaD_hook(lua_State*L,intevent,intline){甲由甸lua_Hookhook=L->hook;甲由甹if(hook&&L->allowhook){甲甲田CallInfo*ci=L->ci;甲甲由ptrdiff_ttop=savestack(L,L->top);甲甲甲ptrdiff_tci_top=savestack(L,ci->top);甲甲申lua_Debugar;甲甲甴ar.
event=event;甲甲电ar.
currentline=line;甲甲甶ar.
i_ci=ci;甲甲男luaD_checkstack(L,LUA_MINSTACK);/*ensureminimumstacksize*/甲甲甸ci->top=L->top+LUA_MINSTACK;甲甲甹lua_assert(ci->topstack_last);甲申田L->allowhook=0;/*cannotcallhooksinsideahook*/甲申由ci->callstatus|=CIST_HOOKED;甲申甲lua_unlock(L);甲申申(*hook)(L,&ar);甲申甴lua_lock(L);甲申电lua_assert(!
L->allowhook);甲申甶L->allowhook=1;甲申男ci->top=restorestack(L,ci_top);甲申甸L->top=restorestack(L,top);甲申甹ci->callstatus&=~CIST_HOOKED;甲甴田}甲甴由}甲甴甲甲甴申甲甴甴staticvoidcallhook(lua_State*L,CallInfo*ci){甲甴电inthook=LUA_HOOKCALL;甲甴甶ci->u.
l.
savedpc++;/*hooksassume'pc'isalreadyincremented*/甲甴男if(isLua(ci->previous)&&这里做的事情并不复杂:保存数据栈、构造调试信息、通过设置畡畬畬畯畷畨畯畯畫禁掉钩子的递归调用,然后调用畃版本的钩子函数,完毕后恢复这些状态.
2120我们可以给一个线程按指令执行条数设置钩子,并在钩子函数中yield出线程.
只需要在外部做一个线程调度器,这样就用lua模拟了一个抢先式的多线程模型.
每个coroutine并不需要主动调用yield挂起自己、钩子会定期做yield操作.
笔者曾经在Lua5.
2的beta期,尝试过这个想法,细节可以参考bloghttp://blog.
codingnow.
com/2011/11/ameba_lua_52.
html.
21此处遗留了一个小问题,是关于lua的尾调用优化时,钩子的正确处理.
甶甮甲线程的执行与中断甶男6.
2.
4从C函数中挂起线程界畵畡电甮甲中挂起一个线程和延续之前挂起线程的过程,远比畬畵畡电甮甲之前的版本要复杂的多.
在阅读这部分代码前,必须先展开一个话题:界畵畡电甮甲解决的一个关键问题是,如何让畃函数正确的配合工作.
畃语言是不支持延续点这个特性的.
如果你从畃函数中利用畬畯畮畧番畭異跳出,就再也回不到跳出点了.
这对界畵畡工作在虚拟机字节码上的大部分特性来说,都不是问题.
但是,異畣畡畬畬和元表都涉及畃函数调用,有这样的限制,让畬畵畡不那么完整.
界畵畡电甮甲应用一系列的技巧,绕开了这个限制,支持了畹畩略畬畤畡畢畬略異畣畡畬畬畡畮畤畭略畴畡畭略畴畨畯畤畳.
在界畵畡电甮甲的文档中,我们可以找到这么一小节:畈畡畮畤畬畩畮畧留畩略畬畤畳畩畮畃就是围绕解决这个难题展开的.
首先我们来看看问题的产生:畲略畳畵畭略的发起总是通过一次畬畵畡畲略畳畵畭略的调用,在界畵畡电甮由以前,畹畩略畬畤的调用必定结束于一次畬畵畡畹畩略畬畤调用,而调用它的畃函数必须立刻返回.
中间不能有任何畃函数执行到中途的状态.
这样,界畵畡畖畍才能正常工作.
用畃甩畬畵畡畲略畳畵畭略→界畵畡畦畵畮畣畴畩畯畮畳→畣畯畲畯畵畴畩畮略甮畹畩略畬畤→用畃甩畬畵畡畹畩略畬畤→用畃甩畲略畴畵畲畮在这个流程中,无论界畵畡畦畵畮畣畴畩畯畮畳有多少层,都被畬畵畡畓畴畡畴略的调用栈管理.
所以当最后畃畲略畴畵畲畮返回到最初畲略畳畵畭略点,都不存在什么问题,可以让下一次畲略畳畵畭略正确继续.
也就是说,在畹畩略畬畤时,畬畵畡调用栈上可以有没有执行完的畬畵畡函数,但不可以有没有执行完的畃函数.
如果我们写了这么一个畃扩展,在畃畦畵畮畣畴畩畯畮里回调了传入的一个界畵畡函数.
情况就变得不一样了.
用畃甩畬畵畡畲略畳畵畭略→界畵畡畦畵畮畣畴畩畯畮→畃畦畵畮畣畴畩畯畮→用畃甩畬畵畡畣畡畬畬→界畵畡畦畵畮畣畴畩畯畮→畣畯畲畯畵畴畩畮略甮畹畩略畬畤→用畃甩畬畵畡畹畩略畬畤畃通过畬畵畡畣畡畬畬调用的界畵畡函数中再调用畣畯畲畯畵畴畩畮略甮畹畩略畬畤会导致在畹畩略畬畤之后,再次畲略畳畵畭略时,不再可能从畬畵畡畣畡畬畬的下一行继续运行.
畬畵畡在遇到这种情况时,会抛出一个异常產畡畴畴略畭異畴畴畯畹畩略畬畤畡畣畲畯畳畳畭略畴畡畭略畴畨畯畤甯畃甭畣畡畬畬畢畯畵畮畤畡畲畹產.
在电甮甲之前,有人试图解决这个问题,去掉畣畯畲畯畵畴畩畮略的这些限制.
比如畃畯畣畯22这个项目.
它用操作系统的协程来解决这个问题23,即给每个畬畵畡畣畯畲畯畵畴畩畮略真的附在一个畃协程上,独立一个畃堆栈.
这样的方案开销较大,且依赖平台特性.
那么,在畃和界畵畡的边界,如果在畹畩略畬畤之后,畲略畳畵畭略如何继续运行畃边界之后的畃代码所有界畵畡线程共用了一个畃堆栈.
可以使用畬畯畮畧番畭異从调用深处跳出来,却无法回到那个位置.
因为一旦跳出,堆栈就被破坏.
畃进入界畵畡的边界一共有四个畁畐畉:畬畵畡畣畡畬畬甬畬畵畡異畣畡畬畬甬畬畵畡畲略畳畵畭略和畬畵畡畹畩略畬畤.
其中要解决的关键问题在于调用一个畬畵畡函数,却可能有两条返回路径.
畬畵畡函数的正常返回应该执行畬畵畡畣畡畬畬调用后面的畃代码,而中途如果畹畩略畬畤发生,会导致执行序回到前面畬畵畡畲略畳畵畭略调用处的下一行畃代码执行.
对于后一种,再次调用畬畵畡畲略畳畵畭略,还需要回到畬畵畡畣畡畬畬之后完成后续的畃执行逻辑.
畃语言是不允许这样做的,因为当初的畃堆栈已经不存在了.
界畵畡电甮甲改造了畁畐畉畬畵畡畣畡畬畬畫来解决这个问题.
既然在畹畩略畬畤之后,畃的执行序无法回到畬畵畡畣畡畬畬畫的下一行代码,那么就让畃语言使用者自己提供一个畃畯畮畴畩畮畵畡畴畩畯畮函数畫来继续.
我们可以这样理解畫这个参数:当畬畵畡畣畡畬畬畫调用的畬畵畡函数中没有发生畹畩略畬畤时,它会正常返回.
一旦发生畹畩略畬畤,调用者要明白,畃代码无法正常延续,而畬畵畡當畭会在需要延续时调用畫来完成后续工作.
畫会得到正确的界保持正确的畬畵畡畳畴畡畴略状态,看起来就好像用一个新的畃执行序替代掉原来的畃执行序一样.
一个容易理解的用法就是在一个畃函数调用的最后使用畬畵畡畣畡畬畬畫:由lua_callk(L,0,LUA_MULTRET,0,k);22CoCo-TrueCCoroutinesforLuahttp://coco.
luajit.
org23CoCo在Windows上使用了Fiber.
甶甸第六章协程及函数的执行甲returnk(L);也就是把畣畡畬畬畫后面的执行逻辑放在一个独立畃函数畫中,分别在畣畡畬畬畫后调用它,或是传递给框架,让框架在畲略畳畵畭略后调用.
这里畬畵畡状态机的状态被正确保存在界中,而畃函数堆栈会在畹畩略畬畤后被破坏掉.
如果我们需要在畫中得到延续点前的畃函数状态怎么办呢畬畵畡提供了畣畴畸用于辅助记录畃中的状态.
在畫中,可以通过畬畵畡畧略畴畣畴畸获得最近一次边界调用时传入的畣畴畸.
同时,畬畵畡畧略畴畣畴畸还会返回原始函数应该返回的值.
这是因为切换到延续点函数中后,原始函数就无法返回了.
利用畬畵畡畧略畴畣畴畸就取到畬畵畡異畣畡畬畬畫原本应该返回的值.
对于没有返回值的原始函数畬畵畡畣畡畬畬畫,它可以返回界畕畁留畉畅界畄用来标识执行序曾被切出,又回到了延续状态中.
界畵畡的线程结构界中保有完整的畃畡畬畬畉畮畦畯调用栈.
当畃层面的调用栈被破坏时,尚未返回的畃函数会于切入畬畵畡虚拟机前在畃畡畬畬畉畮畦畯中留下延续点函数.
原本在畃层面利用原生代码和系统提供的畃堆栈维系的畃函数调用线索,被平坦化为界里畃畡畬畬畉畮畦畯中的一个个延续点函数.
想延续一个畃调用栈被破坏掉的畬畵畡线程,只需要依次调用畃畡畬畬畉畮畦畯中的延续点函数就能完成同样的执行逻辑.
6.
2.
5挂起与延续理解了界畵畡电甮甲对线程挂起和延续的处理方式,再来看相关代码要容易理解一些.
中断并挂起一个线程和线程的执行发生异常,这两种情况对畬畵畡虚拟机的执行来说是类似的.
都是利用畬畵畡畄畴畨畲畯畷回到最近的保护点.
不同的是,线程的状态不同.
主动挂起需要调用畁畐畉畬畵畡畹畩略畬畤畫,把当前执行处的函数保存到畃畡畬畬畉畮畦畯的略畸畴畲畡中,并设置线程状态为界畕畁留畉畅界畄,然后抛出异常.
和异常抛出不同,畹畩略畬畤只可能被畬畵畡畹畩略畬畤畫触发,这是一个畃畁畐畉,而不是畬畵畡的虚拟机的指令.
也就是说,畹畩略畬畤必然来源于某次畃函数调用,从畬畵畡畄畣畡畬畬或畬畵畡畄異畣畡畬畬中退出的.
这比异常的发生点要少的多.
下面先看看畬畵畡畹畩略畬畤畫的实现.
源代码甶甮甲甴町畬畤畯甮畣町畬畵畡畹畩略畬畤畫电电甹LUA_APIintlua_yieldk(lua_State*L,intnresults,intctx,lua_CFunctionk){电甶田CallInfo*ci=L->ci;电甶由luai_userstateyield(L,nresults);电甶甲lua_lock(L);电甶申api_checknelems(L,nresults);电甶甴if(L->nny>0){电甶电if(L!
=G(L)->mainthread)电甶甶luaG_runerror(L,"attempttoyieldacrossaC-callboundary");电甶男else电甶甸luaG_runerror(L,"attempttoyieldfromoutsideacoroutine");电甶甹}电男田L->status=LUA_YIELD;电男由ci->extra=savestack(L,ci->func);/*savecurrent'func'*/电男甲if(isLua(ci)){/*insideahook*/电男申api_check(L,k==NULL,"hookscannotcontinueafteryielding");电男甴}电男电else{甶甮甲线程的执行与中断甶甹电男甶if((ci->u.
c.
k=k)!
=NULL)/*isthereacontinuation*/电男男ci->u.
c.
ctx=ctx;/*savecontext*/电男甸ci->func=L->top-nresults-1;/*protectstackbelowresults*/电男甹luaD_throw(L,LUA_YIELD);电甸田}电甸由lua_assert(ci->callstatus&CIST_HOOKED);/*mustbeinsideahook*/电甸甲lua_unlock(L);电甸申return0;/*returnto'luaD_hook'*/电甸甴}不是所有的畃函数都可以正常恢复,只要调用层次上面有一个这样的畃函数,畹畩略畬畤就无法正确工作.
这是由畮畮畹的值来检测的.
关于畮畮畹参见甶甮甲甮甲节的脚注.
畬畵畡畹畩略畬畤畫是一个公开畁畐畉,只用于给界畵畡程序编写畃扩展模块使用.
所以处于这个函数内部时,一定处于一个畃函数调用中.
但钩子函数的运行是个例外.
钩子函数本身是一个畃函数,但是并不是通常正常的畃畁畐畉调用进来的.
在界畵畡函数中触发钩子会认为当前状态是处于界畵畡函数执行中.
这个时候允许畹畩略畬畤线程,但无法正确的处理畃层面的延续点.
所以禁止传入延续点函数.
而对于正常的畃调用,允许修改延续点畫来改变执行流程.
这里只需要简单的把畫和畣畴畸设入界,其它的活交给畲略畳畵畭略去处理就可以了.
畬畵畡畲略畳畵畭略的过程要复杂的多.
先列出代码,再慢慢分析.
源代码甶甮甲电町畬畤畯甮畣町畬畵畡畲略畳畵畭略电甲甸LUA_APIintlua_resume(lua_State*L,lua_State*from,intnargs){电甲甹intstatus;电申田lua_lock(L);电申由luai_userstateresume(L,nargs);电申甲L->nCcalls=(from)from->nCcalls+1:1;电申申L->nny=0;/*allowyields*/电申甴api_checknelems(L,(L->status==LUA_OK)nargs+1:nargs);电申电status=luaD_rawrunprotected(L,resume,L->top-nargs);电申甶if(status==-1)/*errorcalling'lua_resume'*/电申男status=LUA_ERRRUN;电申甸else{/*yieldorregularerror*/电申甹while(status!
=LUA_OK&&status!
=LUA_YIELD){/*error*/电甴田if(recover(L,status))/*recoverpoint*/电甴由status=luaD_rawrunprotected(L,unroll,NULL);/*runcontinuation*/电甴甲else{/*unrecoverableerror*/电甴申L->status=cast_byte(status);/*markthreadas'dead'*/电甴甴seterrorobj(L,status,L->top);电甴电L->ci->top=L->top;电甴甶break;电甴男}电甴甸}男田第六章协程及函数的执行电甴甹lua_assert(status==L->status);电电田}电电由L->nny=1;/*donotallowyields*/电电甲L->nCcalls--;电电申lua_assert(L->nCcalls==((from)from->nCcalls:0));电电甴lua_unlock(L);电电电returnstatus;电电甶}畬畵畡畲略畳畵畭略开始运行时,等价于一次保护性调用.
所以它是允许直接调用的畃函数畹畩略畬畤的.
这里把畮畮畹设置为田开启.
然后利用对畲略畳畵畭略函数的保护调用来进行前半段工作.
源代码甶甮甲甶町畬畤畯甮畣町畲略畳畵畭略甴甸甹staticvoidresume(lua_State*L,void*ud){甴甹田intnCcalls=L->nCcalls;甴甹由StkIdfirstArg=cast(StkId,ud);甴甹甲CallInfo*ci=L->ci;甴甹申if(nCcalls>=LUAI_MAXCCALLS)甴甹甴resume_error(L,"Cstackoverflow",firstArg);甴甹电if(L->status==LUA_OK){/*maybestartingacoroutine*/甴甹甶if(ci!
=&L->base_ci)/*notinbaselevel*/甴甹男resume_error(L,"cannotresumenon-suspendedcoroutine",firstArg);甴甹甸/*coroutineisinbaselevel;startrunningit*/甴甹甹if(!
luaD_precall(L,firstArg-1,LUA_MULTRET))/*Luafunction*/电田田luaV_execute(L);/*callit*/电田由}电田甲elseif(L->status!
=LUA_YIELD)电田申resume_error(L,"cannotresumedeadcoroutine",firstArg);电田甴else{/*resumingfrompreviousyield*/电田电L->status=LUA_OK;电田甶ci->func=restorestack(L,ci->extra);电田男if(isLua(ci))/*yieldedinsideahook*/电田甸luaV_execute(L);/*justcontinuerunningLuacode*/电田甹else{/*'common'yield*/电由田if(ci->u.
c.
k!
=NULL){/*doesithaveacontinuation*/电由由intn;电由甲ci->u.
c.
status=LUA_YIELD;/*'default'status*/电由申ci->callstatus|=CIST_YIELDED;电由甴lua_unlock(L);电由电n=(*ci->u.
c.
k)(L);/*callcontinuation*/电由甶lua_lock(L);电由男api_checknelems(L,n);甶甮甲线程的执行与中断男由电由甸firstArg=L->top-n;/*yieldresultscomefromcontinuation*/电由甹}电甲田luaD_poscall(L,firstArg);/*finish'luaD_precall'*/电甲由}电甲甲unroll(L,NULL);电甲申}电甲甴lua_assert(nCcalls==L->nCcalls);电甲电}如果畲略畳畵畭略是重新启动一个函数,那么只需要按和畬畵畡畄畣畡畬畬相同的正常的调用流程进行.
若需要延续之前的调用,如上文所述,之前只可能从一次畃调用中触发畬畵畡畹畩略畬畤畫挂起.
但钩子函数是一个特殊情况,它是一个畃函数,却看起来在界畵畡中.
这时从畃畡畬畬畉畮畦畯中的略畸畴畲畡取出上次运行到的函数,可以识别出这个情况.
当它是一个界畵畡调用,那么必然是从钩子函数中切出的,不会有被打断的虚拟机指令,直接通过畬畵畡畖略畸略畣畵畴略继续它的字节码解析执行流程.
若是畃函数,按照延续点的约定,调用延续点畫,之后经过畬畵畡畄異畯畳畣畡畬畬完成这次调用.
这所有事情做完之后,不一定完成了所有的工作.
这是因为之前完整的调用层次,包含在界的畃畡畬畬畉畮畦畯中,而不是存在于当前的畃调用栈上.
如果检查到畬畵畡的调用栈上有未尽的工作,必须完成它.
这项工作可通过畵畮畲畯畬畬函数完成.
源代码甶甮甲男町畬畤畯甮畣町畵畮畲畯畬畬甴甲甶staticvoidunroll(lua_State*L,void*ud){甴甲男UNUSED(ud);甴甲甸for(;;){甴甲甹if(L->ci==&L->base_ci)/*stackisempty*/甴申田return;/*coroutinefinishednormally*/甴申由if(!
isLua(L->ci))/*Cfunction*/甴申甲finishCcall(L);甴申申else{/*Luafunction*/甴申甴luaV_finishOp(L);/*finishinterruptedinstruction*/甴申电luaV_execute(L);/*executedowntohigherC'boundary'*/甴申甶}甴申男}甴申甸}畵畮畲畯畬畬发现界中的当前函数如果是一个界畵畡函数时,由于字节码的解析过程也可能因为触发元方法等情况调用畬畵畡畄畣畡畬畬而从中间中断.
故需要先调用畬畵畡畖甌畮畩畳畨畏異24,再交到畬畵畡畖略畸略畣畵畴略来完成未尽的字节码.
当执行流中断于一次畃函数调用,甌畮畩畳畨畃畣畡畬畬函数能完成当初执行了一半的畃函数的剩余工作.
源代码甶甮甲甸町畬畤畯甮畣町甌畮畩畳畨畃畣畡畬畬甴田由staticvoidfinishCcall(lua_State*L){甴田甲CallInfo*ci=L->ci;24参见一节.
男甲第六章协程及函数的执行甴田申intn;甴田甴lua_assert(ci->u.
c.
k!
=NULL);/*musthaveacontinuation*/甴田电lua_assert(L->nny==0);甴田甶if(ci->callstatus&CIST_YPCALL){/*wasinsideapcall*/甴田男ci->callstatus&=~CIST_YPCALL;/*finish'lua_pcall'*/甴田甸L->errfunc=ci->u.
c.
old_errfunc;甴田甹}甴由田/*finish'lua_callk'/'lua_pcall'*/甴由由adjustresults(L,ci->nresults);甴由甲/*callcontinuationfunction*/甴由申if(!
(ci->callstatus&CIST_STAT))/*nocallstatus*/甴由甴ci->u.
c.
status=LUA_YIELD;/*'default'status*/甴由电lua_assert(ci->u.
c.
status!
=LUA_OK);甴由甶ci->callstatus=(ci->callstatus&~(CIST_YPCALL|CIST_STAT))|CIST_YIELDED;甴由男lua_unlock(L);甴由甸n=(*ci->u.
c.
k)(L);甴由甹lua_lock(L);甴甲田api_checknelems(L,n);甴甲由/*finish'luaD_precall'*/甴甲甲luaD_poscall(L,L->top-n);甴甲申}前面曾提到过,此时线程一定处于健康的状态.
那么之前的工作肯定中止于畬畵畡畣畡畬畬畫或畬畵畡異畣畡畬畬畫.
这时,先应该完成畬畵畡畣畡畬畬畫没完成的工作25:adjustresults(L,ci->nresults);;然后调用畃函数中设置的延续点函数;由于这是一次未完成的畃函数调用,那么一定来源于一次被中断的畬畵畡畄異畲略畣畡畬畬,收尾的工作还剩下luaD_poscall(L,L->top-n);.
当畲略畳畵畭略这前半段工作完成,结果要么是一切顺利,状态码为界畕畁畏畋结束或是界畕畁留畉畅界畄主动挂起.
那么就没有太多剩下的工作.
界的状态是完全正常的.
可当代码中有错误发生时,问题就复杂一些.
从定义上说,畬畵畡畲略畳畵畭略需要具有捕获错误的能力.
同样有这个能力的还有畬畵畡異畣畡畬畬畫.
如果在调用栈上,有畬畵畡異畣畡畬畬畫优先于它捕获错误,那么执行流应该交到畬畵畡異畣畡畬畬畫之后,也就是畬畵畡異畣畡畬畬畫设置的延续点函数.
26.
对畬畵畡畲略畳畵畭略来说,错误被畬畵畡異畣畡畬畬畫捕获了,程序应该继续运行.
它就有责任完成延续点的约定.
这是用畲略畣畯當略畲和畵畮畲畯畬畬函数完成的.
源代码甶甮甲甹町畬畤畯甮畣町畲略畣畯當略畲甴甴甴staticCallInfo*findpcall(lua_State*L){甴甴电CallInfo*ci;甴甴甶for(ci=L->ci;ci!
=NULL;ci=ci->previous){/*searchforapcall*/25luacallk和luapcallk在调用完luaDcall后,后续的代码没有区别,都是adjustresults(L,ci->nresults);,可以一致对待.
26被resume保护起来的执行流程中再调用luapcallk,它是不能直接使用setjmp设置保护点的.
因为如果在这里设置setjmp,有更深层次的函数调用了yield的话.
再次resume时,就在丢失了最初的C调用栈同时,丢失了之前由luapcallk设置的保护点.
缺乏这个设计,导致在Lua5.
1以前无法在pcall的函数中使用yield.
甶甮甲线程的执行与中断男申甴甴男if(ci->callstatus&CIST_YPCALL)甴甴甸returnci;甴甴甹}甴电田returnNULL;/*nopendingpcall*/甴电由}甴电甲甴电申甴电甴staticintrecover(lua_State*L,intstatus){甴电电StkIdoldtop;甴电甶CallInfo*ci=findpcall(L);甴电男if(ci==NULL)return0;/*norecoverypoint*/甴电甸/*"finish"luaD_pcall*/甴电甹oldtop=restorestack(L,ci->extra);甴甶田luaF_close(L,oldtop);甴甶由seterrorobj(L,status,oldtop);甴甶甲L->ci=ci;甴甶申L->allowhook=ci->u.
c.
old_allowhook;甴甶甴L->nny=0;/*shouldbezerotobeyieldable*/甴甶电luaD_shrinkstack(L);甴甶甶L->errfunc=ci->u.
c.
old_errfunc;甴甶男ci->callstatus|=CIST_STAT;/*callhaserrorstatus*/甴甶甸ci->u.
c.
status=status;/*(hereitis)*/甴甶甹return1;/*continuerunningthecoroutine*/甴男田}畲略畣畯當略畲用来把错误引导到调用栈上最近的畬畵畡異畣畡畬畬畫的延续点上.
它首先回溯畃畡畬畬畉畮畦畯栈,找到从畃中调用畬畵畡異畣畡畬畬畫的位置.
这次畬畵畡異畣畡畬畬畫一定从畬畵畡畄異畣畡畬畬中被打断,接下来就必须完成畬畵畡畄異畣畡畬畬本应该完成却没有机会去做的事情.
所以我们会看到,接下来的代码和畬畵畡畄異畣畡畬畬的后半部分非常相似.
最后需要把线程运行状态设上畃畉畓畔畓畔畁畔标记让畵畮畲畯畬畬函数正确的设置线程状态.
接下来只需要保护调用畵畮畲畯畬畬来依据畬畵畡调用栈执行逻辑上后续的流程.
最后回到源代码甶甮甲电,可以看到畬畵畡畲略畳畵畭略比界畵畡电甮由之前的版本多了一个参数畦畲畯畭,它是用来更准确的统计畃调用栈的层级的.
畮畃畣畡畬畬畳的意义在于当发生无穷递归后,界畵畡虚拟机可以先于畃层面的堆栈溢出导致的毁灭性错误之前,捕获到这种情况,安全的抛出异常.
由于现在可以在畃函数中切出,那么发起畲略畳畵畭略的位置可能处于逻辑上调用层次较深的位置.
这就需要调用者传入畲略畳畵畭略的调用来源线程,正确的计算畮畃畣畡畬畬畳.
6.
2.
6luacallk和luapcallk有了这些基础,公开的畁畐畉畬畵畡畣畡畬畬畫和畬畵畡異畣畡畬畬畫就能理解清楚了.
畬畵畡畣畡畬畬畫只是对畬畵畡畄畣畡畬畬的简单封装.
在调用之前,根据需要把延续点畫以及畣畴畸设置到当前的畃畡畬畬畉畮畦畯结构中.
源代码甶甮申田町畬畡異畩甮畣町畬畵畡畣畡畬畬畫男甴第六章协程及函数的执行甸甸甹LUA_APIvoidlua_callk(lua_State*L,intnargs,intnresults,intctx,甸甹田lua_CFunctionk){甸甹由StkIdfunc;甸甹甲lua_lock(L);甸甹申api_check(L,k==NULL||!
isLua(L->ci),甸甹甴"cannotusecontinuationsinsidehooks");甸甹电api_checknelems(L,nargs+1);甸甹甶api_check(L,L->status==LUA_OK,"cannotdocallsonnon-normalthread");甸甹男checkresults(L,nargs,nresults);甸甹甸func=L->top-(nargs+1);甸甹甹if(k!
=NULL&&L->nny==0){/*needtopreparecontinuation*/甹田田L->ci->u.
c.
k=k;/*savecontinuation*/甹田由L->ci->u.
c.
ctx=ctx;/*savecontext*/甹田甲luaD_call(L,func,nresults,1);/*dothecall*/甹田申}甹田甴else/*nocontinuationornoyieldable*/甹田电luaD_call(L,func,nresults,0);/*justdothecall*/甹田甶adjustresults(L,nresults);甹田男lua_unlock(L);甹田甸}畬畵畡異畣畡畬畬畫差不了多少.
如果不需要延续点的支持或是处于不能被挂起的状态,那么,简单的调用畬畵畡畄異畣畡畬畬就可以了.
否则不能设置保护点,而改在调用前设置好延续点以及畣畴畸,并将线程状态标记为畃畉畓畔留畐畃畁界界.
这样在畲略畳畵畭略过程中被畲略畣畯當略畲函数找到.
源代码甶甮申由町畬畡異畩甮畣町畬畵畡異畣畡畬畬畫甹甲甸structCallS{/*datato'f_call'*/甹甲甹StkIdfunc;甹申田intnresults;甹申由};甹申甲甹申申甹申甴staticvoidf_call(lua_State*L,void*ud){甹申电structCallS*c=cast(structCallS*,ud);甹申甶luaD_call(L,c->func,c->nresults,0);甹申男}甹申甸甹申甹甹甴田甹甴由LUA_APIintlua_pcallk(lua_State*L,intnargs,intnresults,interrfunc,甹甴甲intctx,lua_CFunctionk){甶甮甲线程的执行与中断男电甹甴申structCallSc;甹甴甴intstatus;甹甴电ptrdiff_tfunc;甹甴甶lua_lock(L);甹甴男api_check(L,k==NULL||!
isLua(L->ci),甹甴甸"cannotusecontinuationsinsidehooks");甹甴甹api_checknelems(L,nargs+1);甹电田api_check(L,L->status==LUA_OK,"cannotdocallsonnon-normalthread");甹电由checkresults(L,nargs,nresults);甹电甲if(errfunc==0)甹电申func=0;甹电甴else{甹电电StkIdo=index2addr(L,errfunc);甹电甶api_checkstackindex(L,errfunc,o);甹电男func=savestack(L,o);甹电甸}甹电甹c.
func=L->top-(nargs+1);/*functiontobecalled*/甹甶田if(k==NULL||L->nny>0){/*nocontinuationornoyieldable*/甹甶由c.
nresults=nresults;/*doa'conventional'protectedcall*/甹甶甲status=luaD_pcall(L,f_call,&c,savestack(L,c.
func),func);甹甶申}甹甶甴else{/*preparecontinuation(callisalreadyprotectedby'resume')*/甹甶电CallInfo*ci=L->ci;甹甶甶ci->u.
c.
k=k;/*savecontinuation*/甹甶男ci->u.
c.
ctx=ctx;/*savecontext*/甹甶甸/*saveinformationforerrorrecovery*/甹甶甹ci->extra=savestack(L,c.
func);甹男田ci->u.
c.
old_allowhook=L->allowhook;甹男由ci->u.
c.
old_errfunc=L->errfunc;甹男甲L->errfunc=func;甹男申/*markthatfunctionmaydoerrorrecovery*/甹男甴ci->callstatus|=CIST_YPCALL;甹男电luaD_call(L,c.
func,nresults,1);/*dothecall*/甹男甶ci->callstatus&=~CIST_YPCALL;甹男男L->errfunc=ci->u.
c.
old_errfunc;甹男甸status=LUA_OK;/*ifitishere,therewerenoerrors*/甹男甹}甹甸田adjustresults(L,nresults);甹甸由lua_unlock(L);男甶第六章协程及函数的执行甹甸甲returnstatus;甹甸申}6.
2.
7异常处理畬畵畡的内部运行期异常,即错误码为界畕畁畅畒畒畒畕畎的那个,都是直接或间接的由畬畵畡畇略畲畲畯畲畭畳畧抛出的.
按畬畵畡的约定,这类异常会在数据栈上留下错误信息,或是调用一个用户定义的错误处理函数.
畬畵畡畇略畲畲畯畲畭畳畧实现在畬畤略畢畵畧甮畣中.
源代码甶甮申甲町畬畤略畢畵畧甮畣町畬畵畡畇略畲畲畯畲畭畳畧电甶田l_noretluaG_errormsg(lua_State*L){电甶由if(L->errfunc!
=0){/*isthereanerrorhandlingfunction*/电甶甲StkIderrfunc=restorestack(L,L->errfunc);电甶申if(!
ttisfunction(errfunc))luaD_throw(L,LUA_ERRERR);电甶甴setobjs2s(L,L->top,L->top-1);/*moveargument*/电甶电setobjs2s(L,L->top-1,errfunc);/*pushfunction*/电甶甶L->top++;电甶男luaD_call(L,L->top-2,1,0);/*callit*/电甶甸}电甶甹luaD_throw(L,LUA_ERRRUN);电男田}它尝试从界中读出略畲畲畦畵畮畣,并使用畬畵畡畄畣畡畬畬调用它.
如果在略畲畲畦畵畮畣里再次出错,会继续调用自己.
这样就有可能会在错误处理函数中递归下去.
但调用达到一定层次后,畮畃畣畡畬畬畳会超过上限最终产生一个界畕畁畅畒畒畅畒畒中止这个过程.
公开的畁畐畉畬畵畡略畲畲畯畲是对它的简单封装.
源代码甶甮申申町畬畡異畩甮畣町畬畵畡略畲畲畯畲由由田电LUA_APIintlua_error(lua_State*L){由由田甶lua_lock(L);由由田男api_checknelems(L,1);由由田甸luaG_errormsg(L);由由田甹/*codeunreachable;willunlockwhencontrolactuallyleavesthekernel*/由由由田return0;/*toavoidwarnings*/由由由由}第七章虚拟机界畵畡是一门解释型语言.
但在运行时,并不直接解释运行界畵畡的源代码,而是先翻译成一种字节码,这些字节码运行在界畵畡的虚拟机上.
从界畵畡第电版开始,将之前的堆栈式虚拟机重新设计后,重新实现为一种新的基于寄存器的虚拟机1.
同时大量减少了虚拟机指令数量2.
这使得实现界畵畡虚拟机的复杂度也大大降低.
除了官方采用的畃实现外,采用畊畡當畡或畃産等实现界畵畡虚拟机都不是特别困难的事情.
7.
1指令结构界畵畡中的每条指令都由一个申甲位无符号整数表示,指令种类和操作对象都被编码进这个数字.
界畵畡为它定义了一个专门的类型叫做畉畮畳畴畲畵畣畴畩畯畮.
由/*甲**typeforvirtual-machineinstructions申**mustbeanunsignedwith(atleast)4bytes(seedetailsinlopcodes.
h)甴*/电typedeflu_int32Instruction;表示指令种类的部分叫做畯異畣畯畤略,由于操作种类只有甴田种,所以仅需要甶位编码,目前尚留有很大的扩展空间.
每条指令,大都是对一个对象作出影响.
这个受影响的对象被称为畁.
它由一个甸位整数编码进指令中.
畁通常是一个寄存器的索引值;也可能是对畵異當畡畬畵略的操作.
而作用到畁的参数一般有两个,每个参数就由甹位表示,分别称为畂和畃.
下表可以看到指令按位域的布局.
有部分指令不需要两个操作参数,这时,可以把畂和畃合并为一个由甸位的整数畂畸看待,适应更大的范围.
当操作符涉及指令跳转时,例如畏畐畊畍畐,这个参数表示跳转偏移量.
向前跳转需要设定偏移量为一个负数.
这类指令需要参数带有符号信息,此时记作畳畂畸.
界畵畡采用和浮点数的指数表示方式相同的移码来表示有符号的数字.
例如,田被表示为甲17,而甭由被表示为甲17由.
1大部分虚拟机都被实现为堆栈式的,例如传统的Java虚拟机、.
Net虚拟机、Python虚拟机等等.
从历史上看,Lua5是第一个被广泛使用的寄存器式虚拟机;之后诞生的Perl6以及Google为Andriod实现的Delvik虚拟机也采用了基于寄存器的设计.
相比于堆栈式设计,基于寄存器的虚拟机可以用更少的指令数量来表达要进行的操作,但由于需要讲寄存器号编码进指令,所以单条指令的长度也需要更长.
不过Lua的虚拟机指令设计的非常巧妙,用等长的32位数字就可以表示绝大部分指令.
2Lua虚拟机指令数量一度在3.
1版达到了128条,到5.
0发布时,重新设计过的Lua虚拟机指令数仅需要35条了[4].
到Lua5.
2.
2为止,虚拟机一共有40条指令.
男男男甸第七章虚拟机畁畂畃在指令中所占位数以及在指令码中的位偏移量,界畵畡都在畬畯異畣畯畤略畳甮畨中以宏定义的形式给出,可以由程序员配置3.
源代码男甮由町畬畯異畣畯畤略畳甮畨町畯異畣畯畤略畡畲畧申电/*申甶**sizeandpositionofopcodearguments.
申男*/申甸#defineSIZE_C9申甹#defineSIZE_B9甴田#defineSIZE_Bx(SIZE_C+SIZE_B)甴由#defineSIZE_A8甴甲#defineSIZE_Ax(SIZE_C+SIZE_B+SIZE_A)甴申甴甴#defineSIZE_OP6甴电甴甶#definePOS_OP0甴男#definePOS_A(POS_OP+SIZE_OP)甴甸#definePOS_C(POS_A+SIZE_A)甴甹#definePOS_B(POS_C+SIZE_C)电田#definePOS_BxPOS_C电由#definePOS_AxPOS_A7.
1.
1常量在源代码电甮由中,我们可以看到,每个函数原型都绑定有这个函数用到的所有常量.
界畵畡虚拟机在运行时,会将需要的常量加载到寄存器中,然后利用这些寄存器做相应的工作.
加载常量的畯異畣畯畤略为界畏畁畄畋,它有两个参数.
这个操作把畂畸所指的常量加载到畁所指的寄存器中.
界畵畡虚拟机同时支持甲电甶个寄存器4,而这个操作最多可以索引甲18个常量.
在界畵畡电甮由中,单个函数的常量个数受这个畯異畣畯畤略的限制.
由程序员手写的代码很少超过这个限制,但某些机器生成的代码则不然.
界畵畡电甮甲将这个限制放宽到了甲26.
它增加了界畏畁畄畋畘指令,把常量索引号3比如,你可以把B和C参数在指令码中的位置交换用于你的嵌入环境.
这样,标准的Lua字节码反编译程序就会得到错误的结果.
当你不想让别人轻易看到你的软件中嵌入的Lua字节码的含义,这可能是个简单的办法.
当然,对于读过Lua源码的程序员来说,没有太大的意义.
4Lua的寄存器指向当前栈空间.
低位高位甶畢畩畴畳甸畢畩畴畳甹畢畩畴畳甹畢畩畴畳畯異畣畯畤略畁畃畂畁畂畸畁畳畂畸畁畸表男甮由町指令布局男甮由指令结构男甹放在接下来的一条畅畘畔畒畁畁畒畇指令中.
畅畘畔畒畁畁畒畇把操作码以外的所有甲甶位都用于参数表示,把它称为畁畸.
我们可以看成这是界畵畡虚拟机对申甲位指令长度限制的一个扩展.
而当函数引用的常量较少时,界畵畡则采用了另一种优化方案:不必将常量加载到寄存器中再做后续处理,而是让具体指令直接去访问常量表.
某些指令用一种特殊的方式使用畂或畃参数.
这个参数即可以是表示对寄存器号的索引,又可以表示对常量表的索引.
只是,为了区分参数表示的是寄存器号,还是常量编号,需要占用参数中的一个位.
去掉这一位后,畂和畃还能表示田到甲电电的索引,和畁的长度吻合.
下面看一下相关的宏5:源代码男甮甲町畬畯異畣畯畤略畳甮畨町畲畫由甲甹/*由申田**MacrostooperateRKindices由申由*/由申甲由申申/*thisbit1meansconstant(0meansregister)*/由申甴#defineBITRK(1甽畒用畁甩甫由畅畑畁畂畃畩畦用用畒畋用畂甩甽甽畒畋用畃甩甩甽畁甩畴畨略畮異畣甫甫界畔畁畂畃畩畦用用畒畋用畂甩畃甩畴畨略畮異畣甫甫7Lua的布尔运算And和Or支持短路法则,所以它在虚拟机中以分支跳转的形式实现.
在Lua虚拟机的操作码中,看不到And操作和Or操作.
男甮甲字节码的运行甸由名字参数描述畔畅畓畔畓畅畔畁畂畃畩畦用畒用畂甩畃甩畴畨略畮畒用畁甩町甽畒用畂甩略畬畳略異畣甫甫畃畁界界畁畂畃畒用畁甩甬甮甮甮甬畒用畁甫畃甭甲甩町甽畒用畁甩用畒用畁甫由甩甬甮甮甮甬畒用畁甫畂甭由甩甩畔畁畉界畃畁界界畁畂畃畲略畴畵畲畮畒用畁甩用畒用畁甫由甩甬甮甮甮甬畒用畁甫畂甭由甩甩畒畅畔畕畒畎畁畂畲略畴畵畲畮畒用畁甩甬甮甮甮甬畒用畁甫畂甭甲甩用畳略略畮畯畴略甩畆畏畒界畏畏畐畁畳畂畸畒用畁甩甫甽畒用畁甫甲甩画畩畦畒用畁甩ci;电申甶LClosure*cl;电申男TValue*k;电申甸StkIdbase;电申甹newframe:/*reentrypointwhenframechanges(call/return)*/电甴田lua_assert(ci==L->ci);电甴由cl=clLvalue(ci->func);电甴甲k=cl->p->k;电甴申base=ci->u.
l.
base;电甴甴/*mainloopofinterpreter*/电甴电for(;;){电甴甶Instructioni=*(ci->u.
l.
savedpc++);电甴男StkIdra;电甴甸if((L->hookmask&(LUA_MASKLINE|LUA_MASKCOUNT))&&电甴甹(--L->hookcount==0||L->hookmask&LUA_MASKLINE)){电电田Protect(traceexec(L));电电由}电电甲/*WARNING:severalcallsmayreallocthestackandinvalidate'ra'*/电电申ra=RA(i);电电甴lua_assert(base==ci->u.
l.
base);电电电lua_assert(basetop&&L->topstack+L->stacksize);畬畵畡畖略畸略畣畵畴略用一个死循环,依次解析字节码指令.
当前指令畩从畳畡當略畤異畣中取出:由Instructioni=*(ci->u.
l.
savedpc++);12cl是closure的缩写.
男甮甲字节码的运行甸申所有的指令13都会操作寄存器畁.
而对界畵畡虚拟机而言,寄存器即栈上的变量,所以可以将寄存器畁所指变量预先取出放到局部变量畲畡中.
由#defineRA(i)(base+GETARG_A(i))甲申ra=RA(i);但某些操作在运行过程中,会改变数据栈的大小.
而畲畡是一个指向数据栈的指针,而不是一个索引,这种情况下,一旦栈有可能发生变化,就需要重新取畲畡的值.
另外一个和数据栈指针有关的变量是畢畡畳略,也会因为做某些操作和发生变化.
这时就需要重新对畢畡畳略赋值.
这个比畲畡重置要频繁的多,因为畲畡只在一次操作中有效,做下一个操作时,自然会重新读取.
而畢畡畳略则一直需要使用,所以对畢畡畳略的重置被封装到了畐畲畯畴略畣畴宏里.
把需要重置畢畡畳略的代码块包裹在这个宏中.
源代码男甮甴町畬當畭甮畣町畐畲畯畴略畣畴电由由#defineProtect(x){{x;};base=ci->u.
l.
base;}畬畵畡畖略畸略畣畵畴略的内循环中,对于每种操作码编写了一小段代码.
它用的畃语言中的畳畷畩畴畣畨甯畣畡畳略结构.
为了结构表达清晰,界畵畡电甮甲以后,定义了一组宏来表示这种控制结构.
源代码男甮电町畬當畭甮畣町當畭畤畩畳異畡畴畣畨电申田#definevmdispatch(o)switch(o)电申由#definevmcase(l,b)casel:{b}break;电申甲#definevmcasenb(l,b)casel:{b}/*nb=nobreak*/7.
2.
2寄存器赋值界畵畡虚拟机是基于寄存器结构实现的,也就是说,每段界畵畡代码被翻译为一组对甲电甶个寄存器的操作指令.
这有点类似我们为界畵畡编写畃扩展.
畃函数通常是从界中取出参数逐个记录在畃的局部变量中,然后利用畃代码直接对这些值进行操作.
可以把寄存器类比于界畵畡的寄存器,它们的确有相似之处,畃中的局部变量处于畃堆栈上,而界畵畡的寄存器则处于界畵畡的数据栈中.
给局部变量赋值的过程,是由畍畏畖畅界畏畁畄畋界畏畁畄畋畘界畏畁畄畂畏畏界界畏畁畄畎畉界畇畅畔畕畐畖畁界畓畅甭畔畕畐畖畁界这组操作码完成的.
值的来源有三:其一,其它寄存器,即局部变量.
畍畏畖畅可以完成这个工作.
源代码男甮甶町畬當畭甮畣町畏畐畍畏畖畅电电男vmcase(OP_MOVE,电电甸setobjs2s(L,ra,RB(i));电电甹)其二,常量.
畮畩畬和畢畯畯畬类型的数据比较短,可以通过指令直接加载,而勿需通过常量表.
13这里所指所有指令不包括OPEXTRAARG.
且EXTRAARG也不应该看作是一条独立的指令.
因为它的含义由上一条指令决定,属于上一条指令的额外参数.
之所以独立拥有一个操作码,是为了方便检查字节码的有效性.
甸甴第七章虚拟机源代码男甮男町畬當畭甮畣町畏畐界畏畁畄畎畉界畏畐界畏畁畄畂畏畏界电男田vmcase(OP_LOADBOOL,电男由setbvalue(ra,GETARG_B(i));电男甲if(GETARG_C(i))ci->u.
l.
savedpc++;/*skipnextinstruction(ifC)*/电男申)电男甴vmcase(OP_LOADNIL,电男电intb=GETARG_B(i);电男甶do{电男男setnilvalue(ra++);电男甸}while(b--);电男甹)为了减少生成字节码的数量,界畵畡让这两个操作中都承载了比字面上更多的含义.
界畏畁畄畎畉界可以同时把多个变量初始化为畮畩畬.
设想一下,在大量界畵畡代码中,我们都会在函数内声明好几个局部变量.
这些变量的默认值都为畮畩畬.
只要这些变量声明位置在一起,那么在生成字节码时,就可以用一条界畏畁畄畎畉界指令搞定它们.
界畏畁畄畂畏畏界则不仅仅用于简单的将一个布尔常量赋给寄存器.
因为在界畵畡代码中,用一个常量畴畲畵略或是畦畡畬畳略初始化局部变量是比较少的.
更多的是针对逻辑表达式的处理.
比如我们写下面这行代码时候,由localt=(a==b)表面上看,畡甽甽畢是一个表达式.
但是界畵畡虚拟机并没有针对甽甽运算的对应字节代码.
所有逻辑表达式都被翻译为畩畦畴畨略畮略畬畳略的分支结构了.
上面的代码实际更接近这样:由localt甲ifa==bthen申t=true甴else电t=false甶end界畏畁畄畂畏畏界可以根据畃参数来决定是否跳过下一条指令,就可以避免为这种结构生成过多的分支跳转指令.
只需要四条,而不是五条指令来完成上述操作.
由EQ101甲JMP01;to4申LOADBOOL001甴LOADBOOL010对于数字或字符串这样的常量,不可能直接把值编码进指令中.
所以界畵畡为每个函数原型保留有一张常量表,引用这些常量时,只需要给出在常量表中的索引.
界畏畁畄畋就可以把畂畸参数索引的常量加载到畁寄存器中.
如果常量表过大,索引号超过了畂畸可以表达的范围,就使用界畏畁畄畋畘.
源代码男甮甸町畬當畭甮畣町畏畐界畏畁畄畋男甮甲字节码的运行甸电电甶田vmcase(OP_LOADK,电甶由TValue*rb=k+GETARG_Bx(i);电甶甲setobj2s(L,ra,rb);电甶申)电甶甴vmcase(OP_LOADKX,电甶电TValue*rb;电甶甶lua_assert(GET_OPCODE(*ci->u.
l.
savedpc)==OP_EXTRAARG);电甶男rb=k+GETARG_Ax(*ci->u.
l.
savedpc++);电甶甸setobj2s(L,ra,rb);电甶甹)第三,一些既不是常量,又不在寄存器中的数据.
在界畵畡电甮甲中,这类数据仅指畵異當畡畬畵略或存在于某张表中的值.
在历史上,界畵畡有过全局表的概念14.
现在全局变量仅仅是对一个特殊的畵異當畡畬畵略畅畎畖的引用,可以看成是一个语法糖而不是语言的基础特性了.
不过现实是,大量的界畵畡代码会直接引用畅畎畖中的变量,为这种访问方式设计一个独立的操作码有利于字节码的紧凑,提高性能.
源代码男甮甹町畬當畭甮畣町畏畐畇畅畔畕畐畖畁界电甸田vmcase(OP_GETUPVAL,电甸由intb=GETARG_B(i);电甸甲setobj2s(L,ra,cl->upvals[b]->v);电甸申)电甸甴vmcase(OP_GETTABUP,电甸电intb=GETARG_B(i);电甸甶Protect(luaV_gettable(L,cl->upvals[b]->v,RKC(i),ra));电甸男)电甸甸vmcase(OP_GETTABLE,电甸甹Protect(luaV_gettable(L,RB(i),RKC(i),ra));电甹田)电甹由vmcase(OP_SETTABUP,电甹甲inta=GETARG_A(i);电甹申Protect(luaV_settable(L,cl->upvals[a]->v,RKB(i),RKC(i)));电甹甴)电甹电vmcase(OP_SETUPVAL,电甹甶UpVal*uv=cl->upvals[GETARG_B(i)];电甹男setobj(L,uv->v,ra);电甹甸luaC_barrier(L,uv,ra);电甹甹)甶田田vmcase(OP_SETTABLE,甶田由Protect(luaV_settable(L,ra,RKB(i),RKC(i)));14全局表的概念在Lua5.
1之前都一直存在.
从Lua5.
0开始变为环境表.
所以在之前的虚拟机操作码中,有一组独立的操作码GETGLOBAL和SETGLOBAL.
甸甶第七章虚拟机甶田甲)甶田申vmcase(OP_NEWTABLE,甶田甴intb=GETARG_B(i);甶田电intc=GETARG_C(i);甶田甶Table*t=luaH_new(L);甶田男sethvalue(L,ra,t);甶田甸if(b!
=0||c!
=0)甶田甹luaH_resize(L,t,luaO_fb2int(b),luaO_fb2int(c));甶由田checkGC(L,ra+1);甶由由)甶由甲vmcase(OP_SELF,甶由申StkIdrb=RB(i);甶由甴setobjs2s(L,ra+1,rb);甶由电Protect(luaV_gettable(L,rb,RKC(i),ra));甶由甶)畇畅畔畕畐畖畁界和畓畅畔畕畐畖畁界可以读写当前的畵異當畡畬畵略.
畇畅畔畔畁畂畕畐和畓畅畔畔畁畂畕畐则用来读写畵異當畡畬畵略所指的表中的条目15.
畇畅畔畔畁畂界畅和畓畅畔畔畁畂界畅是类似的,但操作的表不在畵異當畡畬畵略中,而在寄存器里.
它们在实现上的区别仅在于,前者畂是对畵異當畡畬畵略的索引,而后者则表示寄存器号.
从文档上看,畓畅界畆在界畵畡语法中是一个语法糖.
但界畵畡虚拟机的确为它做了优化.
a:f()和locala=a;a.
f(a)看起来完整一致,但它们对应的字节码却有区别.
由GETTABUP00-1;_ENV"a"甲SELF00-2;"f"申CALL021如果没有畓畅界畆操作,就需要用两条指令来替代它:由GETTABUP00-1;_ENV"a"甲GETTABLE10-2;"f"申MOVE20甴CALL1217.
2.
3表处理畬畵畡畖畧略畴畴畡畢畬略是对表结构读操作的封装.
在基础的表访问的基础上,增加了元方法的处理.
源代码男甮由田町畬當畭甮畣町畬畵畡畖畧略畴畴畡畢畬略由由田voidluaV_gettable(lua_State*L,constTValue*t,TValue*key,StkIdval){由由由intloop;由由甲for(loop=0;loopmetatable,TM_INDEX))==NULL){/*ornoTM*/由由甹setobj2s(L,val,res);由甲田return;由甲由}由甲甲/*elsewilltrythetagmethod*/由甲申}由甲甴elseif(ttisnil(tm=luaT_gettmbyobj(L,t,TM_INDEX)))由甲电luaG_typeerror(L,t,"index");由甲甶if(ttisfunction(tm)){由甲男callTM(L,tm,t,key,val,1);由甲甸return;由甲甹}由申田t=tm;/*elserepeatwith'tm'*/由申由}由申甲luaG_runerror(L,"loopingettable");由申申}元表的处理最多处理畍畁畘畔畁畇界畏畏畐16层,几乎没有程序会用到这么深的层次,那样会导致性能极其低下.
设置这个上限,主要是为了避免元表的循环引用导致的死循环.
在源代码甴甮由甴中,我们知道,操作元方法比从外部的公开畁畐畉操作元表要快的多.
当操作对象不可以按表的模式去索引时,这里利用畬畵畡畇畴畹異略略畲畲畯畲抛出异常17,中断畬畵畡畖略畸略畣畵畴略的执行.
如果元表中的畩畮畤略畸并不对应一张表,而是一个函数时,就会引发一次元方法调用.
它是由畣畡畬畬畔畍实现的.
源代码男甮由由町畬當畭甮畣町畣畡畬畬畔畍甹申staticvoidcallTM(lua_State*L,constTValue*f,constTValue*p1,甹甴constTValue*p2,TValue*p3,inthasres){甹电ptrdiff_tresult=savestack(L,p3);甹甶setobj2s(L,L->top++,f);/*pushfunction*/甹男setobj2s(L,L->top++,p1);/*1stargument*/甹甸setobj2s(L,L->top++,p2);/*2ndargument*/甹甹if(!
hasres)/*noresult'p3'isthirdargument*/由田田setobj2s(L,L->top++,p3);/*3rdargument*/16MAXTAGLOOP默认为100.
17异常处理见第6.
2.
1节.
甸甸第七章虚拟机由田由/*metamethodmayyieldonlywhencalledfromLuacode*/由田甲luaD_call(L,L->top-(4-hasres),hasres,isLua(L->ci));由田申if(hasres){/*ifhasresult,moveittoitsplace*/由田甴p3=restorestack(L,result);由田电setobjs2s(L,p3,--L->top);由田甶}由田男}所有元方法都有三个参数18.
参数一一定是对象本身,而参数二则根据元方法的不同有所差异.
对于表操作,第二个参数是畫略畹值;而二元运算操作则是第二个参数数.
无论如何,前两个参数一定是输入值,不可以被修改.
而第三个参数则可以是输入,也可以作为输出使用.
畣畡畬畬畔畍的畨畡畳畲略畳参数表示是否需要输出.
有输出时,元方法只有两个输入参数,第三个参数为输出.
畬畵畡畖畳略畴畴畡畢畬略是对表结构写操作的封装.
同样可能触发元方法.
源代码男甮由甲町畬當畭甮畣町畬畵畡畖畳略畴畴畡畢畬略由申甶voidluaV_settable(lua_State*L,constTValue*t,TValue*key,StkIdval){由申男intloop;由申甸for(loop=0;loopmetatable,TM_NEWINDEX))==NULL&&由甴甸/*nometamethod;isthereapreviousentryinthetable*/由甴甹(oldval!
=luaO_nilobject||由电田/*nopreviousentry;mustcreateone.
(Thenexttestis由电由alwaystrue;weonlyneedtheassignment.
)*/由电甲(oldval=luaH_newkey(L,h,key),1)))){由电申/*nometamethodand(now)thereisanentrywithgivenkey*/由电甴setobj2t(L,oldval,val);/*assignnewvaluetothatentry*/由电电invalidateTMcache(h);由电甶luaC_barrierback(L,obj2gco(h),val);由电男return;由电甸}由电甹/*elsewilltrythemetamethod*/18call元方法是个例外,它不是在这里处理的.
源代码6.
19的luaDprecall函数会尝试把对一个对象的函数调用行为,转换为对其call元方法对应函数的调用.
男甮甲字节码的运行甸甹由甶田}由甶由else/*notatable;checkmetamethod*/由甶甲if(ttisnil(tm=luaT_gettmbyobj(L,t,TM_NEWINDEX)))由甶申luaG_typeerror(L,t,"index");由甶甴/*thereisametamethod*/由甶电if(ttisfunction(tm)){由甶甶callTM(L,tm,t,key,val,0);由甶男return;由甶甸}由甶甹t=tm;/*elserepeatwith'tm'*/由男田}由男由luaG_runerror(L,"loopinsettable");由男甲}值得注意是这么两行:由invalidateTMcache(h);甲luaC_barrierback(L,obj2gco(h),val);界畵畡对元表的方法做了优化,畩畮當畡畬畩畤畡畴略畔畍畣畡畣畨略在甴甮甴节有介绍.
由于表内容的更改有可能导致界畵畡内其它对象的生命期变化,所以需要调用畬畵畡畃畢畡畲畲畩略畲畢畡畣畫.
这涉及垃圾收集模块的工作.
7.
2.
4表达式运算界畵畡支持加减乘除四则运算,以及乘方和取模这些二元运算;同时还有取负、取反、对象取长度这几个一元运算操作;另外,针对字符串类型,有字符串连接操作.
源代码男甮由申町畬當畭甮畣町略畸異畲略畳畳畩畯畮畳甶由男vmcase(OP_ADD,甶由甸arith_op(luai_numadd,TM_ADD);甶由甹)甶甲田vmcase(OP_SUB,甶甲由arith_op(luai_numsub,TM_SUB);甶甲甲)甶甲申vmcase(OP_MUL,甶甲甴arith_op(luai_nummul,TM_MUL);甶甲电)甶甲甶vmcase(OP_DIV,甶甲男arith_op(luai_numdiv,TM_DIV);甶甲甸)甶甲甹vmcase(OP_MOD,甶申田arith_op(luai_nummod,TM_MOD);甶申由)甹田第七章虚拟机甶申甲vmcase(OP_POW,甶申申arith_op(luai_numpow,TM_POW);甶申甴)甶申电vmcase(OP_UNM,甶申甶TValue*rb=RB(i);甶申男if(ttisnumber(rb)){甶申甸lua_Numbernb=nvalue(rb);甶申甹setnvalue(ra,luai_numunm(L,nb));甶甴田}甶甴由else{甶甴甲Protect(luaV_arith(L,ra,rb,rb,TM_UNM));甶甴申}甶甴甴)甶甴电vmcase(OP_NOT,甶甴甶TValue*rb=RB(i);甶甴男intres=l_isfalse(rb);/*nextassignmentmaychangethisvalue*/甶甴甸setbvalue(ra,res);甶甴甹)甶电田vmcase(OP_LEN,甶电由Protect(luaV_objlen(L,ra,RB(i)));甶电甲)甶电申vmcase(OP_CONCAT,甶电甴intb=GETARG_B(i);甶电电intc=GETARG_C(i);甶电甶StkIdrb;甶电男L->top=base+c+1;/*marktheendofconcatoperands*/甶电甸Protect(luaV_concat(L,c-b+1));甶电甹ra=RA(i);/*'luav_concat'mayinvokeTMsandmovethestack*/甶甶田rb=b+base;甶甶由setobjs2s(L,ra,rb);甶甶甲checkGC(L,(ra>=rbra+1:rb));甶甶申L->top=ci->top;/*restoretop*/甶甶甴)这些操作很类似,都是以一个或两个对象为操作对象,经过运算后,把结果置入寄存器畁中.
对于数值类型之间的运算,界畵畡做了一些优化,不会判断和触发元方法.
这样可以提高处理效率.
这部分是用宏来实现的.
源代码男甮由甴町畬當畭甮畣町畡畲畩畴畨畯異电甲田#definearith_op(op,tm){\电甲由TValue*rb=RKB(i);\男甮甲字节码的运行甹由电甲甲TValue*rc=RKC(i);\电甲申if(ttisnumber(rb)&&ttisnumber(rc)){\电甲甴lua_Numbernb=nvalue(rb),nc=nvalue(rc);\电甲电setnvalue(ra,op(L,nb,nc));\电甲甶}\电甲男else{Protect(luaV_arith(L,ra,rb,rc,tm));}}而对于其它类型,则会调用畬畵畡畖畡畲畩畴畨.
源代码男甮由电町畬當畭甮畣町畬畵畡畖畡畲畩畴畨申甶田voidluaV_arith(lua_State*L,StkIdra,constTValue*rb,申甶由constTValue*rc,TMSop){申甶甲TValuetempb,tempc;申甶申constTValue*b,*c;申甶甴if((b=luaV_tonumber(rb,&tempb))!
=NULL&&申甶电(c=luaV_tonumber(rc,&tempc))!
=NULL){申甶甶lua_Numberres=luaO_arith(op-TM_ADD+LUA_OPADD,nvalue(b),nvalue(c));申甶男setnvalue(ra,res);申甶甸}申甶甹elseif(!
call_binTM(L,rb,rc,ra,op))申男田luaG_aritherror(L,rb,rc);申男由}二元运算的元方法触发规则略微复杂,所以用畣畡畬畬畢畩畮畔畍对畣畡畬畬畔畍做了一些封装.
源代码男甮由甶町畬當畭甮畣町畣畡畬畬畢畩畮畔畍由男电staticintcall_binTM(lua_State*L,constTValue*p1,constTValue*p2,由男甶StkIdres,TMSevent){由男男constTValue*tm=luaT_gettmbyobj(L,p1,event);/*tryfirstoperand*/由男甸if(ttisnil(tm))由男甹tm=luaT_gettmbyobj(L,p2,event);/*trysecondoperand*/由甸田if(ttisnil(tm))return0;由甸由callTM(L,tm,p1,p2,res,1);由甸甲return1;由甸申}先判断第一个对象是否有需要的元方法,如果找不到,则去第二个对象上面查找.
倘若找不到元方法,又不是基本的数值类型,畬畵畡畖畡畲畩畴畨会用畬畵畡畇畡畲畩畴畨略畲畲畯畲抛出异常.
畕畎畍这个取负操作略微不同,它是一个一元运算.
但处理方式没有太大区别,只需要把参数复制一遍即可.
甹甲第七章虚拟机界畅畎操作用于取对象长度,根据界畵畡的定义:对于字符串取串的长度;对于表,再没有定义元方法时,取数组部分长度;其它情况调用元畬略畮方法.
这里实现了畬畵畡畖畯畢番畬略畮.
这个内部畁畐畉还用于和畃交互的畁畐畉畬畵畡畬略畮的实现.
源代码男甮由男町畬當畭甮畣町畣畡畬畬畢畩畮畔畍申申电voidluaV_objlen(lua_State*L,StkIdra,constTValue*rb){申申甶constTValue*tm;申申男switch(ttypenv(rb)){申申甸caseLUA_TTABLE:{申申甹Table*h=hvalue(rb);申甴田tm=fasttm(L,h->metatable,TM_LEN);申甴由if(tm)break;/*metamethodbreakswitchtocallit*/申甴甲setnvalue(ra,cast_num(luaH_getn(h)));/*elseprimitivelen*/申甴申return;申甴甴}申甴电caseLUA_TSTRING:{申甴甶setnvalue(ra,cast_num(tsvalue(rb)->len));申甴男return;申甴甸}申甴甹default:{/*trymetamethod*/申电田tm=luaT_gettmbyobj(L,rb,TM_LEN);申电由if(ttisnil(tm))/*nometamethod*/申电甲luaG_typeerror(L,rb,"getlengthof");申电申break;申电甴}申电电}申电甶callTM(L,tm,rb,rb,ra,1);申电男}和界畅畎类似的操作是字符串连接操作畃畏畎畃畁畔甬但做的工作要复杂许多.
畃畏畎畃畁畔操作从语义上来说,是把畒用畂甩到畒用畃甩之间的所有值,都以字符串方式连接起来,把结果放到畒用畁甩中.
这个连接过程是通过畬畵畡畖畣畯畮畣畡畴函数完成的.
界畵畡另有一个公开畁畐畉畬畵畡畣畯畮畣畡畴,需要完成类似的工作.
但是畬畵畡畣畯畮畣畡畴的语义要简单一些,只需要把栈顶的若干变量连接起来,并将这些变量弹出,然后将结果压入堆栈.
两者相较,后者可以实现的高效一些.
所以畃畏畎畃畁畔遵循了后一种语义.
所以这个操作和前面提到的其它运算不同,它可能会改变畒用畂甩到畒用畃甩之间寄存器的值,甚至畒用畃甩之后寄存器的值.
其实际的工作方式是,把通过临时修改栈顶地址为畃,然后连接畒用畂甩到畒用畃甩的值,其结果存于畒用畂甩.
再将畒用畂甩复制到畒用畁甩,最后将栈顶位置调整回去.
至于畒用畂甩到畒用畃甩以及之后的寄存器,不能被后续指令读取.
换句话说,畒用畂甩到畒用畃甩寄存器必须工作在栈顶.
这一点是由界畵畡的字节码编译部分保证的.
例如这样一段界畵畡代码:由locala="1"甲localb="2"申localc=a.
.
b男甮甲字节码的运行甹申就需要先把畡和畢复制到栈顶寄存器,然后再执行畃畏畎畃畁畔操作.
界畵畡为它生成的字节码如下:由LOADK0-1;"1"甲LOADK1-2;"2"申MOVE20甴MOVE31电CONCAT223由于字符串连接操作,可能触发元方法,进而导致堆栈空间扩展.
在畬畵畡畖畣畯畮畣畡畴调用完毕后,畲畡可能不再指向原来的位置.
故而需要重新获取畲畡甽畒畁用畩甩.
对于这种需要额外申请内存的操作,需要用畣畨略畣畫畇畃做步进回收工作.
在畃畏畎畃畁畔操作的最后,重置了数据栈的栈顶.
我们应该这样理解数据栈的栈顶:界畵畡字节码以寄存器的方式来理解数据栈空间,大多数情况下,用到多少寄存器是在生成字节码的编译期决定的.
所以在函数原型结构里有畭畡畸畳畴畡畣畫畳畩畺略这个信息,同时在运行时,会把这段空间的顶端记录在畃畡畬畬畉畮畦畯的畴畯異中.
虚拟机运行是随机访问这段栈空间的.
但界畵畡虚拟机在运行时,也会以堆栈的方式利用这个数据栈,这里的畬畵畡畖畣畯畮畣畡畴就是这样.
这种以栈形式利用数据堆栈都是临时行为,使用完毕后应该重置数据栈顶.
最后,来看看畬畵畡畖畣畯畮畣畡畴的实现:源代码男甮由甸町畬當畭甮畣町畬畵畡畖畣畯畮畣畡畴甲甹申voidluaV_concat(lua_State*L,inttotal){甲甹甴lua_assert(total>=2);甲甹电do{甲甹甶StkIdtop=L->top;甲甹男intn=2;/*numberofelementshandledinthispass(atleast2)*/甲甹甸if(!
(ttisstring(top-2)||ttisnumber(top-2))||!
tostring(L,top-1)){甲甹甹if(!
call_binTM(L,top-2,top-1,top-2,TM_CONCAT))申田田luaG_concaterror(L,top-2,top-1);申田由}申田甲elseif(tsvalue(top-1)->len==0)/*secondoperandisempty*/申田申(void)tostring(L,top-2);/*resultisfirstoperand*/申田甴elseif(ttisstring(top-2)&&tsvalue(top-2)->len==0){申田电setobjs2s(L,top-2,top-1);/*resultissecondop.
*/申田甶}申田男else{申田甸/*atleasttwonon-emptystringvalues;getasmanyaspossible*/申田甹size_ttl=tsvalue(top-1)->len;申由田char*buffer;申由由inti;申由甲/*collecttotallength*/申由申for(i=1;ilen;申由电if(l>=(MAX_SIZET/sizeof(char))-tl)甹甴第七章虚拟机申由甶luaG_runerror(L,"stringlengthoverflow");申由男tl+=l;申由甸}申由甹buffer=luaZ_openspace(L,&G(L)->buff,tl);申甲田tl=0;申甲由n=i;申甲甲do{/*concatallstrings*/申甲申size_tl=tsvalue(top-i)->len;申甲甴memcpy(buffer+tl,svalue(top-i),l*sizeof(char));申甲电tl+=l;申甲甶}while(--i>0);申甲男setsvalue2s(L,top-n,luaS_newlstr(L,buffer,tl));申甲甸}申甲甹total-=n-1;/*got'n'stringstocreate1new*/申申田L->top-=n-1;/*popped'n'stringsandpushedone*/申申由}while(total>1);/*repeatuntilonly1resultleft*/申申甲}它每次至少处理两个元素.
如果其中有数值类型,就地通过畴畯畳畴畲畩畮畧转换为字符串.
畴畯畳畴畲畩畮畧是一个宏,当寄存器内值类型为数字时,调用畬畵畡畖畴畯畳畴畲畩畮畧将其转换为字符串.
源代码男甮由甹町畬當畭甮畣町畬畵畡畖畴畯畳畴畲畩畮畧甴男intluaV_tostring(lua_State*L,StkIdobj){甴甸if(!
ttisnumber(obj))甴甹return0;电田else{电由chars[LUAI_MAXNUMBER2STR];电甲lua_Numbern=nvalue(obj);电申intl=lua_number2str(s,n);电甴setsvalue2s(L,obj,luaS_newlstr(L,s,l));电电return1;电甶}电男}当这两个元素中有至少有一个即不是字符串,又不是数字的话,就会触发畣畯畮畣畡畴元方法.
否则就继续向下检测,统计后续的字符串或数字的长度.
多个这两种类型的值,可以一次连接在一起,连接过程不会产生函数调用.
统计尽可能多的连续可连接量,统计它们的总长度,然后利用畬畵畡畚畯異略畮畳異畡畣略在畇用界甩的畢畵甋域中分配一块临时的空间19,足够存放下结果串.
最后,在这个空间上做字符串连接操作即可.
重复这个过程,就可以把所有要求的寄存器内容以字符串形式连接在一起.
19在2.
2.
3节,介绍了这个临时空间.
男甮甲字节码的运行甹电7.
2.
5分支和跳转界畵畡虚拟机定义了畅畑界畔界畅畔畅畓畔畔畅畓畔畓畅畔五个分支操作.
前面提到过,我们不能以单条申甲位的指令来看待条件分支指令.
而应该把分支指令之后的跳转指令畊畍畐看作是一体的.
即,当条件成立时,继续运行;条件不成立时,跳转到指定位置.
畊畍畐也可以单独做无条件跳转指令使用.
跳转地址使用的是相对量,负数表示向前跳转,零表示下一条指令,依次类推.
无条件跳转和条件跳转分别用畤畯番畵畭異及畤畯畮略畸畴番畵畭異两个宏实现.
源代码男甮甲田町畬當畭甮畣町畤畯番畵畭異电田由/*executeajumpinstruction*/电田甲#definedojump(ci,i,e)\电田申{inta=GETARG_A(i);\电田甴if(a>0)luaF_close(L,ci->u.
l.
base+a-1);\电田电ci->u.
l.
savedpc+=GETARG_sBx(i)+e;}畤畯畮略畸畴番畵畭異读出下一条指令,其必定是畊畍畐.
这里并没有立刻递增畳畡當略畤異畣的值.
而是让随后的畤畯番畵畭異对畳畡當略畤異畣的偏移多加由.
由于畤畯番畵畭異是用宏实现的,可以认为多传递一个参数略并不会影响效率.
而合并对畳畡當略畤異畣的修改执行效率要略微高一点.
畤畯番畵畭異除了偏移畳畡當略畤異畣以跳转执行流以外,当畁大于田时,还会调用畬畵畡畆畣畬畯畳略关闭畁层次的畵異當畡畬畵略20.
在界畵畡电甮由以前,畊畍畐操作并无这个职责,它仅仅修改畳畡當略畤異畣.
但界畵畡电甮由有另一个操作码畃界畏畓畅.
若畊畍畐操作会跳出一个代码块时,就生成一条畃界畏畓畅操作的指令来调用畬畵畡畆畣畬畯畳略.
畃界畏畓畅操作总是伴随着畊畍畐,界畵畡电甮甲对虚拟机指令集做了优化,去掉了畃界畏畓畅,把这个操作合并到了畊畍畐中.
畊畍畐指令就是简单的调用畤畯番畵畭異.
源代码男甮甲由町畬當畭甮畣町畏畐畊畍畐甶甶电vmcase(OP_JMP,甶甶甶dojump(ci,i,0);甶甶男)畅畑界畔界畅的处理非常类似,它们都有对应的畃畁畐畉,所以在畬當畭甮畣中都以畬畵畡畖畁畐畉的形式实现.
在虚拟机执行函数中,就是对这些内部畁畐畉的调用判断条件是否成立.
当条件不成立时,则执行畤畯畮略畸畴番畵畭異.
源代码男甮甲甲町畬當畭甮畣町畏畐畅畑甶甶甸vmcase(OP_EQ,甶甶甹TValue*rb=RKB(i);甶男田TValue*rc=RKC(i);甶男由Protect(甶男甲if(cast_int(equalobj(L,rb,rc))!
=GETARG_A(i))甶男申ci->u.
l.
savedpc++;甶男甴else甶男电donextjump(ci);20upvalue的关闭操作,参见5.
3.
1节.
甹甶第七章虚拟机甶男甶)甶男男)甶男甸vmcase(OP_LT,甶男甹Protect(甶甸田if(luaV_lessthan(L,RKB(i),RKC(i))!
=GETARG_A(i))甶甸由ci->u.
l.
savedpc++;甶甸甲else甶甸申donextjump(ci);甶甸甴)甶甸电)甶甸甶vmcase(OP_LE,甶甸男Protect(甶甸甸if(luaV_lessequal(L,RKB(i),RKC(i))!
=GETARG_A(i))甶甸甹ci->u.
l.
savedpc++;甶甹田else甶甹由donextjump(ci);甶甹甲)甶甹申)对于畅畑操作,类型不同则不同,类型相同则用畬畵畡畖略畱畵畡畬畯畢番判定.
源代码男甮甲申町畬當畭甮畨町略畱畵畡畬畯畢番甲田#defineequalobj(L,o1,o2)(ttisequal(o1,o2)&&luaV_equalobj_(L,o1,o2))源代码男甮甲甴町畬當畭甮畣町畬畵畡畖略畱畵畡畬畯畢番甲电男/*甲电甸**equalityofLuavalues.
L==NULLmeansrawequality(nometamethods)甲电甹*/甲甶田intluaV_equalobj_(lua_State*L,constTValue*t1,constTValue*t2){甲甶由constTValue*tm;甲甶甲lua_assert(ttisequal(t1,t2));甲甶申switch(ttype(t1)){甲甶甴caseLUA_TNIL:return1;甲甶电caseLUA_TNUMBER:returnluai_numeq(nvalue(t1),nvalue(t2));甲甶甶caseLUA_TBOOLEAN:returnbvalue(t1)==bvalue(t2);/*truemustbe1!
!
*/甲甶男caseLUA_TLIGHTUSERDATA:returnpvalue(t1)==pvalue(t2);甲甶甸caseLUA_TLCF:returnfvalue(t1)==fvalue(t2);甲甶甹caseLUA_TSHRSTR:returneqshrstr(rawtsvalue(t1),rawtsvalue(t2));甲男田caseLUA_TLNGSTR:returnluaS_eqlngstr(rawtsvalue(t1),rawtsvalue(t2));甲男由caseLUA_TUSERDATA:{甲男甲if(uvalue(t1)==uvalue(t2))return1;男甮甲字节码的运行甹男甲男申elseif(L==NULL)return0;甲男甴tm=get_equalTM(L,uvalue(t1)->metatable,uvalue(t2)->metatable,TM_EQ);甲男电break;/*willtryTM*/甲男甶}甲男男caseLUA_TTABLE:{甲男甸if(hvalue(t1)==hvalue(t2))return1;甲男甹elseif(L==NULL)return0;甲甸田tm=get_equalTM(L,hvalue(t1)->metatable,hvalue(t2)->metatable,TM_EQ);甲甸由break;/*willtryTM*/甲甸甲}甲甸申default:甲甸甴lua_assert(iscollectable(t1));甲甸电returngcvalue(t1)==gcvalue(t2);甲甸甶}甲甸男if(tm==NULL)return0;/*noTM*/甲甸甸callTM(L,tm,t1,t2,L->top,1);/*callTM*/甲甸甹return!
l_isfalse(L->top);甲甹田}这个函数严格按界畵畡手册中的定义实现,对每种类型分别比较.
从代码中可以看出,畮畩畬畮畵畭畢略畲畢畯畯畬略畡畮畬畩畧畨畴畵畳略畲畤畡畴畡畴畨畲略畡畤畦畵畮畣畴畩畯畮类型都是值比较21,可以在畏用由甩下完成.
字符串被分为短字符串和长字符串分别处理,短字符串直接比较指针,长字符串则可能触发完整的比较操作22.
畵畳略畲畤畡畴畡和畴畡畢畬略都有可能触发比较元方法.
按界畵畡手册中的定义,畅畑操作的元方法触发原则是:被比较的两个对象比较有相同的元表,否则认为它们不相等.
这个过程由畧略畴略畱畵畡畬畔畍函数保证.
源代码男甮甲电町畬當畭甮畣町畧略畴略畱畵畡畬畔畍由甸甶staticconstTValue*get_equalTM(lua_State*L,Table*mt1,Table*mt2,由甸男TMSevent){由甸甸constTValue*tm1=fasttm(L,mt1,event);由甸甹constTValue*tm2;由甹田if(tm1==NULL)returnNULL;/*nometamethod*/由甹由if(mt1==mt2)returntm1;/*samemetatables=>samemetamethods*/由甹甲tm2=fasttm(L,mt2,event);由甹申if(tm2==NULL)returnNULL;/*nometamethod*/由甹甴if(luaV_rawequalobj(tm1,tm2))/*samemetamethods*/由甹电returntm1;21thread和function未在case中出现,它们走的default的gc对象指针的比较流程.
22字符串比较的细节参见3.
2.
1节.
甹甸第七章虚拟机由甹甶returnNULL;由甹男}畬畵畡畖略畱畵畡畬畯畢番同时兼任了畲畡畷略畱畵畡畬畯畢番的职责,当不需要触发元方法时,将界参数传空即可.
源代码男甮甲甶町畬當畭甮畨町畬畵畡畖畲畡畷略畱畵畡畬畯畢番甲甲#defineluaV_rawequalobj(o1,o2)equalobj(NULL,o1,o2)界畔操作由畬畵畡畖畬略畳畳畴畨畡畮完成,界畅操作由畬畵畡畖畬略畳畳略畱畵畡畬完成.
之所以没有大于操作,是因为大于可以由小于等于取反得到.
源代码男甮甲男町畬當畭甮畣町畬畵畡畖畬略畳畳畴畨畡畮畬畵畡畖畬略畳畳略畱畵畡畬甲申由intluaV_lessthan(lua_State*L,constTValue*l,constTValue*r){甲申甲intres;甲申申if(ttisnumber(l)&&ttisnumber(r))甲申甴returnluai_numlt(L,nvalue(l),nvalue(r));甲申电elseif(ttisstring(l)&&ttisstring(r))甲申甶returnl_strcmp(rawtsvalue(l),rawtsvalue(r))=0)/*firsttry'le'*/甲电田returnres;甲电由elseif((res=call_orderTM(L,r,l,TM_LT))tsv.
len;甲由甲constchar*r=getstr(rs);甲由申size_tlr=rs->tsv.
len;甲由甴for(;;){甲由电inttemp=strcoll(l,r);甲由甶if(temp!
=0)returntemp;甲由男else{/*stringsareequaluptoa'\0'*/甲由甸size_tlen=strlen(l);/*indexoffirst'\0'inbothstrings*/甲由甹if(len==lr)/*risfinished*/甲甲田return(len==ll)0:1;甲甲由elseif(len==ll)/*lisfinished*/甲甲甲return-1;/*lissmallerthanr(becauserisnotfinished)*/甲甲申/*bothstringslongerthan'len';gooncomparing(afterthe'\0')*/甲甲甴len++;甲甲电l+=len;ll-=len;r+=len;lr-=len;甲甲甶}甲甲男}甲甲甸}由于界畵畡的字符串是'\0'安全的,字符串中也可能包含'\0'.
一次畳畴畲畣畯畬畬调用并不一定得到结果.
每次用畳畴畲畣畯畬畬比较完,假若字符串相等,就要跳过'\0'继续比较,直到达到畔畓畴畲畩畮畧中记录的字符串长度为止.
7.
2.
6函数调用界畵畡中的函数调用有两种,一种是标准的函数调用,它会需要生成新的一层调用栈,执行函数流程,然后弹出调用栈返回.
另一种叫做尾调用,它是对标准函数调用的优化.
尾调用不生成新的调用栈,而不复用当前的.
在大多数函数式编程语言中,都需要对尾调用做特别优化.
因为函数式语言特别依赖函数的层层调用,甚至用尾调用的方式来做循环.
传统方式每次函数调用都需要生成新的栈帧,容易造成栈溢出.
普通的函数调用,是用畃畁界界操作实现的.
源代码男甮甲甹町畬當畭甮畣町畏畐畃畁界界男田甹vmcase(OP_CALL,男由田intb=GETARG_B(i);男由由intnresults=GETARG_C(i)-1;男由甲if(b!
=0)L->top=ra+b;/*elsepreviousinstructionsettop*/男由申if(luaD_precall(L,ra,nresults)){/*Cfunction*/男由甴if(nresults>=0)L->top=ci->top;/*adjustresults*/男由电base=ci->u.
l.
base;由田田第七章虚拟机男由甶}男由男else{/*Luafunction*/男由甸ci=L->ci;男由甹ci->callstatus|=CIST_REENTRY;男甲田gotonewframe;/*restartluaV_executeovernewLuafunction*/男甲由}男甲甲)函数调用的流程细节在甶甮甲甮甲节有分析,这里只是从畂畃中取出参数和返回值的个数,然后调用畬畵畡畄異畲略畣畡畬畬.
畂为田时,表示传入参数是不定数量的,那么实际参数就由栈顶到函数对象的位置畁的距离决定.
当畂大于田时,参数个数为畂甭由,此时需要临时调整数据栈顶指针为畲畡甫畢,以适应畬畵畡畄異畲略畣畡畬畬的要求.
当畃为田时,返回值是变长的,数量不可预期.
界畵畡把这种调用成为畯異略畮畣畡畬畬.
这种情况只发生在链式调用,即把一个函数的返回值,作为另一个接收变长参数的界畵畡函数的参数的调用;以及尾调用,还有利用函数返回值去初始化一张表的情况.
若畃大于田,则明确接收函数产生的返回值中的畃甭由个.
如果函数是一个畃函数,那么在畬畵畡畄異畲略畣畡畬畬完成后,函数已经调用完毕.
如果不是畯異略畮畣畡畬畬,就需要把数据栈顶指针复位(对应前面修改数据栈顶指针的行为).
否则,留待后续的处理24.
对畬畵畡畄異畲略畣畡畬畬的调用无法用畐畲畯畴略畣畴宏包裹起来(需要取得返回值),畢畡畳略值有可能被修改,故需要显式写一行畢畡畳略变量的重置.
界畵畡函数的调用很简单,前面的畬畵畡畄異畲略畣畡畬畬已经生成好新的畃畡畬畬畉畮畦畯结构.
只需要给其调用状态畣畡畬畬畳畴畡畴畵畳设置畃畉畓畔畒畅畅畎畔畒留标记,表示这次界畵畡函数是由界畵畡函数本身驱动的,然后跳转到畬畵畡畖略畸略畣畵畴略开头继续运行即可.
畃畉畓畔畒畅畅畎畔畒留运行标记可以确保被调用的界畵畡函数在返回时,不会跳出畬畵畡畖略畸略畣畵畴略的内部循环.
尾调用指函数最后以调用另一个函数的形式结束.
这样另一个函数的返回值就可以看作当前函数的返回值.
界畵畡的编译模块在生成这类代码的字节码时,会专门为这种情况生成畔畁畉界畃畁界界的操作码.
单独为尾调用优化,可以节省最后一步参数传递的开销,而且一旦发生尾调用,当前函数已经不再需要数据栈和调用栈,新的调用层次直接复用它们即可.
源代码男甮申田町畬當畭甮畣町畏畐畔畁畉界畃畁界界男甲申vmcase(OP_TAILCALL,男甲甴intb=GETARG_B(i);男甲电if(b!
=0)L->top=ra+b;/*elsepreviousinstructionsettop*/男甲甶lua_assert(GETARG_C(i)-1==LUA_MULTRET);男甲男if(luaD_precall(L,ra,LUA_MULTRET))/*Cfunction*/男甲甸base=ci->u.
l.
base;男甲甹else{男申田/*tailcall:putcalledframe(n)inplaceofcallerone(o)*/男申由CallInfo*nci=L->ci;/*calledframe*/男申甲CallInfo*oci=nci->previous;/*callerframe*/男申申StkIdnfunc=nci->func;/*calledfunction*/男申甴StkIdofunc=oci->func;/*callerfunction*/24在SETLIST操作的结束点上,重置了栈顶指针.
如果是链式调用或尾调用,那么是不关心栈顶指针的(反正调用前都需要改写一次).
男甮甲字节码的运行由田由男申电/*laststackslotfilledby'precall'*/男申甶StkIdlim=nci->u.
l.
base+getproto(nfunc)->numparams;男申男intaux;男申甸/*closeallupvaluesfrompreviouscall*/男申甹if(cl->p->sizep>0)luaF_close(L,oci->u.
l.
base);男甴田/*movenewframeintooldone*/男甴由for(aux=0;nfunc+auxu.
l.
base=ofunc+(nci->u.
l.
base-nfunc);/*correctbase*/男甴甴oci->top=L->top=ofunc+(L->top-nfunc);/*correcttop*/男甴电oci->u.
l.
savedpc=nci->u.
l.
savedpc;男甴甶oci->callstatus|=CIST_TAIL;/*functionwastailcalled*/男甴男ci=L->ci=oci;/*removenewframe*/男甴甸lua_assert(L->top==oci->u.
l.
base+getproto(ofunc)->maxstacksize);男甴甹gotonewframe;/*restartluaV_executeovernewLuafunction*/男电田}男电由)尾调用必须是一次畯異略畮畣畡畬畬,所以畃必须为田.
对畬畵畡畄異畲略畣畡畬畬的调用,返回值参数个数也就写死为界畕畁畍畕界畔畒畅畔了.
被调用函数是一个畃函数时,处理和畃畁界界操作没有什么不同.
当它是一个界畵畡函数时,就需要复用当前栈帧.
首先,关闭当前栈帧上的畵異當畡畬畵略,原本这个步骤应该由畒畅畔畕畒畎来完成的.
但因为发生尾调用时,当前栈帧上的变量已经结束了它们的生命期,并将被新的函数复用空间,所以畬畵畡畆畣畬畯畳略这个操作是需要提前做的.
然后,将畬畵畡畄異畲略畣畡畬畬为新一层函数调用生成的调用栈,以及在新一层数据栈上准备好的参数,都复制到当前栈帧上.
尾调用的行为还应该在畣畡畬畬畳畴畡畴畵畳上特别标记为畃畉畓畔畔畁畉界,这样调试钩子才能正确识别.
做好这个工作后,把畣畩设回当前栈帧,然后跳转到畬畵畡畖略畸略畣畵畴略开头继续运行就好了.
返回操作畒畅畔畕畒畎只针对界畵畡函数,和畃函数无关.
界畵畡函数返回需要调用畬畵畡畆畣畬畯畳略关闭畯異略畮畵異當畡畬畵略25.
当返回值数量不定时,保留栈顶的位置;如果数量明确,则按返回参数数量调整栈顶位置,以便让畬畵畡畄異畯畳畣畡畬畬可以正确调整返回值在当前栈帧上的位置.
这时,检查畃畉畓畔畒畅畅畎畔畒留标记,如果没有这个标记,表示当次调用是从畃里面产生.
这样,直接让畬畵畡畄略畸略畣畵畴略返回即可.
否则,在明确参数个数的情况下重置数据栈顶26,然后跳转到畬畵畡畄略畸略畣畵畴略开头继续循环即可.
7.
2.
7不定长参数不定长参数这个概念,只是针对界畵畡函数.
因为界畵畡提供给畃扩展用的畁畐畉中,没有针对不定长参数的函数.
25参见5.
3.
1节26返回参数不定时,需要保留当前栈顶位置,以便让后续代码知道有几个返回值.
由田甲第七章虚拟机在生成界畵畡函数的字节码时,编译器可以获知这个函数是否需要处理不定数量的参数.
在函数原型畐畲畯畴畯的数据结构中27,畩畳當畡畲畡畲畧这个字段标识了这个函数是否需要不定数量的参数.
用畬畵畡畄異畲略畣畡畬畬发起对一个界畵畡函数的调用时,发起调用者可能准备了不定数量的参数.
这种情况往往是由于前几条指令产生了不定数量的参数导致的.
这可能是由一次畯異略畮畣畡畬畬,所调用的函数返回了不定数量的参数;也可以是在界畵畡中用甮甮甮引用不定数量的参数,它会生成畖畁畒畁畒畇这个操作的字节码.
但畬畵畡畄異畲略畣畡畬畬不用理会传入参数的个数约定,它只需要统计出真实参数个数即可.
但无论是哪种情况,参数都在数据栈顶一直排列到畲畡处.
畬畵畡畄異畲略畣畡畬畬查看被调用的函数,一旦发现它是一个界畵畡函数,并需要处理不定个数的参数的话,它就要把固定的参数个数复制到新创建出的栈帧上,保留不定数量的部分在上一个栈帧的末尾.
细节见源代码甶甮由甹.
如果被调用函数是一个畃函数,或它不需要这些,就不用做这些处理.
综合这些,可以知道,当一个函数被执行,它若需要引用甮甮甮的话,这些数据的位置就在上一层数据栈的末尾,即,当前栈帧的底畢畡畳略就处在这组参数的末端.
由于当前的畃畡畬畬畉畮畦畯结构里还记录了当前正在运行的函数在数据栈上的位置,它指向所有传入参数的开头.
畖畁畒畁畒畇指令可以把甮甮甮参数中的若干个复制到当前栈帧.
源代码男甮申由町畬當畭甮畣町畏畐畖畁畒畁畒畇甸甴甲vmcase(OP_VARARG,甸甴申intb=GETARG_B(i)-1;甸甴甴intj;甸甴电intn=cast_int(base-ci->func)-cl->p->numparams-1;甸甴甶if(btop=ra+n;甸电由}甸电甲for(j=0;jtop-ra)-1;甸由男if(c==0){甸由甸lua_assert(GET_OPCODE(*ci->u.
l.
savedpc)==OP_EXTRAARG);甸由甹c=GETARG_Ax(*ci->u.
l.
savedpc++);甸甲田}甸甲由luai_runtimecheck(L,ttistable(ra));甸甲甲h=hvalue(ra);甸甲申last=((c-1)*LFIELDS_PER_FLUSH)+n;甸甲甴if(last>h->sizearray)/*needsmorespace*/甸甲电luaH_resizearray(L,h,last);/*pre-allocateitatonce*/甸甲甶for(;n>0;n--){甸甲男TValue*val=ra+n;甸甲甸luaH_setint(L,h,last--,val);甸甲甹luaC_barrierback(L,obj2gco(h),val);甸申田}甸申由L->top=ci->top;/*correcttop(incaseofpreviousopencall)*/甸申甲)畂是一次需要复制的数据的个数,畃是偏移量.
畓畅畔界畉畓畔是为了某些机器生成的代码制造的海量数据准备的,如果畃(只有甹位)超过范围的话,可以利用接下来的畅畘畔畒畁畁畒畇来获得更大范围的畃.
对于大量数据的初始化,是一段段的调用畓畅畔界畉畓畔的,这样可以避免单次占用太大的数据栈空间.
所以在计算偏移量时,对畃做了一个倍率界畆畉畅界畄畓畐畅畒畆界畕畓畈28.
当畂为田时,即复制数据栈顶到畲畡的所有数据,这和前面处理不定长参数的约定一致.
畓畅畔界畉畓畔最后一定会重置数据栈顶,如果之前有对不定参数的处理,那么它会安全的把数据栈顶指针复位.
7.
2.
8生成闭包在界畵畡中,函数是一等公民.
一切代码都是函数,准确说是闭包.
当我们执行一段程序时,其实是调用一个函数.
加载一个库,也是调用一个函数.
加载一个界畵畡程序文件,里面即使定义了许多界畵畡函数,但它的整体依旧是单个函数.
28LFIELDSPERFLUSH默认为50.
由田甴第七章虚拟机所以,每段完整的字节码都是一个界畵畡函数.
而每个函数里可以附有很多个函数原型29.
函数原型没有放在常量表里,而是单独成表.
这是因为,它们不可以被界畵畡代码直接使用.
只有函数原型和畵異當畡畬畵略绑定在一起时,成为了闭包,才是界畵畡虚拟机可以处理的对象.
函数原型在生成包含它们的函数的代码被加载时,生成在内存中.
单个函数原型可以被多次绑定,生成多个闭包对象.
这个过程是由畃界畏畓畕畒畅操作完成的.
源代码男甮申申町畬當畭甮畣町畏畐畃界畏畓畕畒畅甸申申vmcase(OP_CLOSURE,甸申甴Proto*p=cl->p->p[GETARG_Bx(i)];甸申电Closure*ncl=getcached(p,cl->upvals,base);/*cachedclosure*/甸申甶if(ncl==NULL)/*nomatch*/甸申男pushclosure(L,p,cl->upvals,base,ra);/*createanewone*/甸申甸else甸申甹setclLvalue(L,ra,ncl);/*pushcashedclosure*/甸甴田checkGC(L,ra+1);甸甴由)在界畵畡电甮由以前,无论你是否绑定畵異當畡畬畵略,绑定怎样的畵異當畡畬畵略,每次都会生成新的闭包.
界畵畡电甮甲中,做了一点优化.
它缓存了上次生成的闭包,如果可能,就重复利用.
这对函数式编程特别有效,因为当你返回一个没有任何畵異當畡畬畵略的纯函数,或是只绑定有全部变量的函数时,不会生成新的实例.
畧略畴畣畡畣畨略畤用来检查是否有缓存过的闭包,以及这次需要绑定的畵異當畡畬畵略是否和缓存体一致.
源代码男甮申甴町畬當畭甮畣町畧略畴畣畡畣畨略畤申男甴/*申男电**checkwhethercachedclosureinprototype'p'maybereused,thatis,申男甶**whetherthereisacachedclosurewiththesameupvaluesneededby申男男**newclosuretobecreated.
申男甸*/申男甹staticClosure*getcached(Proto*p,UpVal**encup,StkIdbase){申甸田Closure*c=p->cache;申甸由if(c!
=NULL){/*isthereacachedclosure*/申甸甲intnup=p->sizeupvalues;申甸申Upvaldesc*uv=p->upvalues;申甸甴inti;申甸电for(i=0;iv;申甸男if(c->l.
upvals[i]->v!
=v)申甸甸returnNULL;/*wrongupvalue;cannotreuseclosure*/申甸甹}申甹田}申甹由returnc;/*returncachedclosure(orNULLifnocachedclosure)*/29参见5.
1节男甮甲字节码的运行由田电申甹甲}为了让畵異當畡畬畵略可比较,函数原型中记录了畵異當畡畬畵略的描述信息畕異當畡畬畤略畳畣结构.
源代码男甮申电町畬畯畢番略畣畴甮畨町畕異當畡畬畤略畳畣甴甴甶typedefstructUpvaldesc{甴甴男TString*name;/*upvaluename(fordebuginformation)*/甴甴甸lu_byteinstack;/*whetheritisinstack*/甴甴甹lu_byteidx;/*indexofupvalue(instackorinouterfunction'slist)*/甴电田}Upvaldesc;畩畮畳畴畡畣畫描述了函数将引用的这个畵異當畡畬畵略是否恰好处于定义这个函数的函数中.
这时,畵異當畡畬畵略是这个外层函数的局部变量,它位于数据栈上.
畩畤畸指的是畵異當畡畬畵略的序号.
对于关闭的畵異當畡畬畵略,已经无法从栈上取到,畩畤畸指外层函数的畵異當畡畬畵略表中的索引号;对于在数据栈上的畵異當畡畬畵略,序号即变量对应的寄存器号.
在界畵畡电甮由以前,闭包不被缓存,所以无须对畵異當畡畬畵略做比较,也就不需要这些信息.
在畐畲畯畴畯结构中,仅保存有供调试信息用的畵異當畡畬畵略名;界畵畡电甮甲则把名字信息转移到畕異當畡畬畤略畳畣结构内了.
比较引用的畵異當畡畬畵略是否相同,按畩畮畳畴畡畣畫标记分开处理就好了.
相同的畵異當畡畬畵略地址也一定相同.
全部畵異當畡畬畵略都一致的话,缓存内的闭包就可以复用.
此时,调用畳略畴畣畬界當畡畬畵略把它赋给畲畡即可.
否则,需要调用異畵畳畨畣畬畯畳畵畲略生成一个新的闭包,并更新缓存.
源代码男甮申甶町畬當畭甮畣町異畵畳畨畣畬畯畳畵畲略申甹电/*申甹甶**createanewLuaclosure,pushitinthestack,andinitialize申甹男**itsupvalues.
Notethatthecallto'luaC_barrierproto'mustcome申甹甸**beforetheassignmentto'p->cache',asthefunctionneedsthe申甹甹**originalvalueofthatfield.
甴田田*/甴田由staticvoidpushclosure(lua_State*L,Proto*p,UpVal**encup,StkIdbase,甴田甲StkIdra){甴田申intnup=p->sizeupvalues;甴田甴Upvaldesc*uv=p->upvalues;甴田电inti;甴田甶Closure*ncl=luaF_newLclosure(L,nup);甴田男ncl->l.
p=p;甴田甸setclLvalue(L,ra,ncl);/*anchornewclosureinstack*/甴田甹for(i=0;il.
upvals[i]=luaF_findupval(L,base+uv[i].
idx);甴由甲else/*getupvaluefromenclosingfunction*/甴由申ncl->l.
upvals[i]=encup[uv[i].
idx];甴由甴}由田甶第七章虚拟机甴由电luaC_barrierproto(L,p,ncl);甴由甶p->cache=ncl;/*saveitoncacheforreuse*/甴由男}绑定畵異當畡畬畵略生成闭包的过程,我们在电甮申甮由节讨论过.
異畵畳畨畣畬畯畳畵畲略的最后,在更新畣畡畣畨略指针前,需要调用畬畵畡畃畢畡畲畲畩略畲異畲畯畴畯,这是因为更换缓存对象,可能引起旧的被缓存闭包的生命期变更.
关于垃圾收集的细节不在这里展开.
7.
2.
9For循环界畵畡支持两种畦畯畲循环.
一种比较简单,就是简单的数字循环;另一种可以支持迭代器.
由forv=e1,e2,e3doblockend界畵畡手册中这样就是这类循环的实现:由do甲localvar,limit,step=tonumber(e1),tonumber(e2),tonumber(e3)申ifnot(varandlimitandstep)thenerror()end甴while(step>0andvar=limit)do电localv=var甶block男var=var+step甸end甹end固然可以用已有的操作去模拟这套实现,但不够高效.
界畵畡的虚拟机提供了两个单独的操作为其提供支持.
在循环开始的时候,當畡畲畬畩畭畩畴和畳畴略異应处于畲畡畲畡甫由畲畡甫甲处.
用一条畆畏畒畐畒畅畐检查它们是否有效,然后立刻跳转到循环代码块尾部的畆畏畒界畏畏畐指令上,检查结束条件.
畆畏畒界畏畏畐在条件成立时,就会循环这个过程.
源代码男甮申男町畬當畭甮畣町畆畏畒畐畒畅畐男男甸vmcase(OP_FORPREP,男男甹constTValue*init=ra;男甸田constTValue*plimit=ra+1;男甸由constTValue*pstep=ra+2;男甸甲if(!
tonumber(init,ra))男甸申luaG_runerror(L,LUA_QL("for")"initialvaluemustbeanumber");男甸甴elseif(!
tonumber(plimit,ra+1))男甸电luaG_runerror(L,LUA_QL("for")"limitmustbeanumber");男甸甶elseif(!
tonumber(pstep,ra+2))男甸男luaG_runerror(L,LUA_QL("for")"stepmustbeanumber");男甮甲字节码的运行由田男男甸甸setnvalue(ra,luai_numsub(L,nvalue(ra),nvalue(pstep)));男甸甹ci->u.
l.
savedpc+=GETARG_sBx(i);男甹田)由于畆畏畒界畏畏畐每次都会递增當畡畲值,所以畆畏畒畐畒畅畐预先把當畡畲减去畳畴略異.
畆畏畒界畏畏畐会根据畳畴略異是正还是负,来区别对待结束条件.
由于循环变量當畡畲很可能用于代码内部,所以生成的循环体代码不直接使用當畡畲对应的寄存器畲畡,而是由畆畏畒界畏畏畐每次复制一份當畡畲到畲畡甫申,循环体就可以安全的修改它了.
源代码男甮申甸町畬當畭甮畣町畆畏畒界畏畏畐男甶男vmcase(OP_FORLOOP,男甶甸lua_Numberstep=nvalue(ra+2);男甶甹lua_Numberidx=luai_numadd(L,nvalue(ra),step);/*incrementindex*/男男田lua_Numberlimit=nvalue(ra+1);男男由if(luai_numlt(L,0,step)luai_numle(L,idx,limit)男男甲:luai_numle(L,limit,idx)){男男申ci->u.
l.
savedpc+=GETARG_sBx(i);/*jumpback*/男男甴setnvalue(ra,idx);/*updateinternalindex.
.
.
*/男男电setnvalue(ra+3,idx)andexternalindex*/男男甶}男男男)迭代器型的畦畯畲循环要复杂一些.
界畵畡手册里是这样解释的:由forvar_1,···,var_ninexplistdoblockend等价于由do甲localf,s,var=explist申whiletruedo甴localvar_1,···,var_n=f(s,var)电ifvar_1==nilthenbreakend甶var=var_1男block甸end甹end实际生成的字节码,迭代器调用在代码块的尾部.
在循环体开头,用一条畊畍畐指令,直接跳转到尾部.
localvar_1,···,var_n=f(s,var)这个过程对应于畔畆畏畒畃畁界界这个操作.
而后判断循环是否结束,并在未结束时移动當畡畲参数,并跳转到代码块开头继续循环的过程由畔畆畏畒界畏畏畐承担.
这两条指令从流程上总是挨在一起的,但由于畔畆畏畒畃畁界界会引发畬畵畡畄畣畡畬畬有可能被打断,所以分解成两个操作来完成更简单.
由田甸第七章虚拟机源代码男甮申甹町畬當畭甮畣町畔畆畏畒畃畁界界畔畆畏畒界畏畏畐男甹由vmcasenb(OP_TFORCALL,男甹甲StkIdcb=ra+3;/*callbase*/男甹申setobjs2s(L,cb+2,ra+2);男甹甴setobjs2s(L,cb+1,ra+1);男甹电setobjs2s(L,cb,ra);男甹甶L->top=cb+3;/*func.
+2args(stateandindex)*/男甹男Protect(luaD_call(L,cb,GETARG_C(i),1));男甹甸L->top=ci->top;男甹甹i=*(ci->u.
l.
savedpc++);/*gotonextinstruction*/甸田田ra=RA(i);甸田由lua_assert(GET_OPCODE(i)==OP_TFORLOOP);甸田甲gotol_tforloop;甸田申)甸田甴vmcase(OP_TFORLOOP,甸田电l_tforloop:甸田甶if(!
ttisnil(ra+1)){/*continueloop*/甸田男setobjs2s(L,ra,ra+1);/*savecontrolvariable*/甸田甸ci->u.
l.
savedpc+=GETARG_sBx(i);/*jumpback*/甸田甹}甸由田)从源代码可以看出,畔畆畏畒畃畁界界做了一点优化.
它顺带会承担畔畆畏畒界畏畏畐的工作.
检查结束条件,如果没有结束,就跳转.
当循环次数很多,且不被中断时,虚拟机只解释了畔畆畏畒畃畁界界而没有处理畔畆畏畒畃畁界界和畔畆畏畒界畏畏畐两条指针.
7.
2.
10协程的中断和延续在甶甮甲甮电节,我们讨论过协程可能在畃层面中断畬畵畡畖略畸略畣畵畴略的运行.
也就是说,畬畵畡畖略畸略畣畵畴略除了正常的在畒畅畔畕畒畎操作中返回外,处理许多操作时,都可能直接跳出.
为了让畃中的畹畩略畬畤跳出协程后,还可以回来继续执行虚拟机中的字节码.
光是依靠畳畡當略畤異畣记住当前的指令位置是不够的.
我们还需要利用畬畵畡畖甌畮畩畳畨畏異来补全被中断的操作未做完的事情.
好在每个操作的跳出点大多是唯一的,就是那次对畬畵畡畄畣畡畬畬的调用,所以处理起来还算简单.
下面,我们来看看畬畵畡畖甌畮畩畳畨畏異的实现.
源代码男甮甴田町畬當畭甮畣町畬畵畡畖甌畮畩畳畨畏異甴甲田/*甴甲由**finishexecutionofanopcodeinterruptedbyanyield甴甲甲*/甴甲申voidluaV_finishOp(lua_State*L){甴甲甴CallInfo*ci=L->ci;甴甲电StkIdbase=ci->u.
l.
base;甴甲甶Instructioninst=*(ci->u.
l.
savedpc-1);/*interruptedinstruction*/男甮甲字节码的运行由田甹甴甲男OpCodeop=GET_OPCODE(inst);甴甲甸switch(op){/*finishitsexecution*/甴甲甹caseOP_ADD:caseOP_SUB:caseOP_MUL:caseOP_DIV:甴申田caseOP_MOD:caseOP_POW:caseOP_UNM:caseOP_LEN:甴申由caseOP_GETTABUP:caseOP_GETTABLE:caseOP_SELF:{甴申甲setobjs2s(L,base+GETARG_A(inst),--L->top);甴申申break;甴申甴}甴申电caseOP_LE:caseOP_LT:caseOP_EQ:{甴申甶intres=!
l_isfalse(L->top-1);甴申男L->top--;甴申甸/*metamethodshouldnotbecalledwhenoperandisK*/甴申甹lua_assert(!
ISK(GETARG_B(inst)));甴甴田if(op==OP_LEusing"u.
l.
savedpc)==OP_JMP);甴甴甴if(res!
=GETARG_A(inst))/*conditionfailed*/甴甴电ci->u.
l.
savedpc++;/*skipjumpinstruction*/甴甴甶break;甴甴男}甴甴甸caseOP_CONCAT:{甴甴甹StkIdtop=L->top-1;/*topwhen'call_binTM'wascalled*/甴电田intb=GETARG_B(inst);/*firstelementtoconcatenate*/甴电由inttotal=cast_int(top-1-(base+b));/*yettoconcatenate*/甴电甲setobj2s(L,top-2,top);/*putTMresultinproperposition*/甴电申if(total>1){/*arethereelementstoconcat*/甴电甴L->top=top-1;/*topisoneafterlastelement(attop-2)*/甴电电luaV_concat(L,total);/*concatthem(mayyieldagain)*/甴电甶}甴电男/*movefinalresulttofinalposition*/甴电甸setobj2s(L,ci->u.
l.
base+GETARG_A(inst),L->top-1);甴电甹L->top=ci->top;/*restoretop*/甴甶田break;甴甶由}甴甶甲caseOP_TFORCALL:{甴甶申lua_assert(GET_OPCODE(*ci->u.
l.
savedpc)==OP_TFORLOOP);甴甶甴L->top=ci->top;/*correcttop*/甴甶电break;甴甶甶}甴甶男caseOP_CALL:{由由田第七章虚拟机甴甶甸if(GETARG_C(inst)-1>=0)/*nresults>=0*/甴甶甹L->top=ci->top;/*adjustresults*/甴男田break;甴男由}甴男甲caseOP_TAILCALL:caseOP_SETTABUP:caseOP_SETTABLE:甴男申break;甴男甴default:lua_assert(0);甴男电}甴男甶}这里面最为复杂的只有畃畏畎畃畁畔的处理.
这是因为畃畏畎畃畁畔有可能引发多次中断.
这里就可以解释,为什么在畬畵畡畖畣畯畮畣畡畴的循环体内,每做完一个步骤,都需要修正数据栈顶指针.
而不是循环结束后,一次性修改.
这样,才可以在函数运行被中断后,可以正确延续.
第八章内置库的实现界畵畡电甮甲自带了几个库,实现了一般应用最基本的需求.
这些库的实现仅仅使用了界畵畡官方手册中提到的畁畐畉,对界畵畡核心部分的代码几乎没有依赖,所以最易于阅读.
阅读这些库的实现,也可以加深对界畵畡畁畐畉的印象,方便我们自己扩展界畵畡.
界畵畡电甮甲简化了界畵畡电甮由中模块组织方式,这也使得代码更为简短.
这一章,就从这里开始.
8.
1从math模块看Lua的模块注册机制数学库是最简单的一个.
它导入了若干数学函数,和两个常量異畩与畨畵畧略.
我们先看看它如何把一组畁畐畉以及常量导入界畵畡的.
源代码甸甮由町畬畭畡畴畨畬畩畢甮畣町畭畡畴畨畬畩畢甲申申staticconstluaL_Regmathlib[]={甲申甴{"abs",math_abs},甲申电{"acos",math_acos},甲申甶{"asin",math_asin},甲申男{"atan2",math_atan2},甲申甸{"atan",math_atan},甲申甹{"ceil",math_ceil},甲甴田{"cosh",math_cosh},甲甴由{"cos",math_cos},甲甴甲{"deg",math_deg},甲甴申{"exp",math_exp},甲甴甴{"floor",math_floor},我没有列完这段代码,因为后面是雷同的.
界畵畡使用一个结构luaL_Reg数组来描述需要注入的函数和名字.
结构体前缀是畬畵畡界而不是畬畵畡,是因为这并非界畵畡的核心畁畐畉部分.
利用luaL_newlib可以把这组函数注入一个畴畡畢畬略.
代码见下面:源代码甸甮甲町畬畭畡畴畨畬畩畢甮畣町畬畵畡畯異略畮畭畡畴畨甲男由LUAMOD_APIintluaopen_math(lua_State*L){甲男甲luaL_newlib(L,mathlib);甲男申lua_pushnumber(L,PI);甲男甴lua_setfield(L,-2,"pi");由由由由由甲第八章内置库的实现甲男电lua_pushnumber(L,HUGE_VAL);甲男甶lua_setfield(L,-2,"huge");甲男男return1;甲男甸}luaL_newlib是定义在畬畡畵畸畬畩畢甮畨里的一个宏,在源代码甸甮申中我们将看到它仅仅是创建了一个畴畡畢畬略,然后把数组里的函数放进去而已.
这个畁畐畉在界畵畡的公开手册里已有明确定义的.
源代码甸甮申町畬畡畵畸畬畩畢甮畨町畬畵畡界畮略畷畬畩畢由田甸#defineluaL_newlibtable(L,l)\由田甹lua_createtable(L,0,sizeof(l)/sizeof((l)[0])-1)由由田由由由#defineluaL_newlib(L,l)(luaL_newlibtable(L,l),luaL_setfuncs(L,l,0))注入这些函数使用的是界畵畡电甮甲新加的畁畐畉luaL_setfuncs,引入这个畁畐畉是因为界畵畡电甮甲取消了环境.
那么,为了让畃函数可以有附加一些额外的信息,就需要利用畵異當畡畬畵略1界畵畡电甮甲简化了畃扩展模块的定义方式,不再要求模块创建全局表.
对于畃模块,以畬畵畡畯異略畮为前缀导出畁畐畉,通常是返回一张存有模块内函数的表.
这可以精简设计,界畵畡中畲略畱畵畩畲略的行为仅仅只是用来加载一个预定义的模块,并阻止重复加载而已;而不用关心载入的模块内的函数放在哪里2.
luaL_setfuncs在源代码甸甮甴里列出了实现,正如手册里所述,它把数组畬中的所有函数注册入栈顶的畴畡畢畬略,并给所有的函数绑上畮畵異个畵異當畡畬畵略.
源代码甸甮甴町畬畡畵畸畬畩畢甮畣町畬畵畡界畳略畴畦畵畮畣畳甸甴甸LUALIB_APIvoidluaL_setfuncs(lua_State*L,constluaL_Reg*l,intnup){甸甴甹luaL_checkversion(L);甸电田luaL_checkstack(L,nup,"toomanyupvalues");甸电由for(;l->name!
=NULL;l+fillthetablewithgivenfunctions*/甸电甲inti;甸电申for(i=0;ifunc,nup);/*closurewiththoseupvalues*/甸电甶lua_setfield(L,-(nup+2),l->name);甸电男}甸电甸lua_pop(L,nup);/*removeupvalues*/甸电甹}1给C函数绑上upvalue取代之前给C函数使用的环境表,是Lua作者推荐的做法[3].
不过要留意:Lua5.
2引入了轻量C函数的概念,没有upvalue的C函数将是一个和lightuserdata一样轻量的值.
不给不必要的C函数绑上upvalue可以使Lua程序得到一定的优化.
为了把需求不同的C函数区别对待,可以通过多次调用luaL_setfuncs来实现.
2Lua5.
1引入了模块机制,要求编写模块的人提供模块名.
对于C模块,模块名通过luaL_openlib设置,Lua模块则是通过module函数.
Lua将以这个模块名在全局表中创建同名的table以存放模块内的API.
这些设计相对繁杂,在5.
2版中已被废弃,代码和文档都因此简洁了不少.
甸甮甲畍畁畔畈模块畁畐畉的实现由由申8.
2math模块API的实现畭畡畴畨模块内的各个数学函数的实现中规中矩,就是使用的界畵畡手册里给出的畁畐畉来实现的.
界畵畡的扩展方式是编写一个原型为intlua_CFunction(lua_State*L)的函数.
界对于使用者来说,不必关心其内部结构.
实际上,公开畁畐畉定义所在的畬畵畡甮畨中也没有lua_State的结构定义.
对于一个用畃编写的系统,模块化设计的重点在于接口的简洁和稳定.
数据结构的细节和内存布局最好能藏在实现层面,界畵畡的畁畐畉设计在这方面做了一个很好的示范.
这个函数通常不会也不建议被畃程序的其它部分直接调用,所以一般藏在源文件内部,以畳畴畡畴畩畣修饰之.
界畵畡的畃函数以堆栈的形式和界畵畡虚拟机交换数据,由一系列畁畐畉从界中取出值,经过一番处理,压回界中的堆栈.
具体的使用方式见界畵畡手册畛电畝.
阅读这部分代码也能增进了解.
源代码甸甮电町畬畵畡畣畯畮畦甮畨町畬畭畡畴畨畯異甴田甸/*甴田甹@@l_mathopallowstheadditionofan'l'or'f'toallmathoperations甴由田*/甴由由#definel_mathop(x)(x)稍微值得留意的是,在这里还定义了一个宏畬畭畡畴畨畯異.
可以看出界畵畡在可定制性上的考虑.
当你想把界畵畡的畎畵畭畢略畲类型修改为畬畯畮畧畤畯畵畢畬略时,便可以通过修改这个宏定义,改变操作畎畵畭畢略畲的畃函数.
比如使用畳畩畮畬(或是使用畳畩畮畦操作甍畯畡畴类型)而不是畳畩畮3.
我们再看另一小段代码:畭畡畴畨甮畬畯畧的实现:源代码甸甮甶町畬畭畡畴畨畬畩畢甮畣町畬畯畧由由甸staticintmath_log(lua_State*L){由由甹lua_Numberx=luaL_checknumber(L,1);由甲田lua_Numberres;由甲由if(lua_isnoneornil(L,2))由甲甲res=l_mathop(log)(x);由甲申else{由甲甴lua_Numberbase=luaL_checknumber(L,2);由甲电if(base==(lua_Number)10.
0)res=l_mathop(log10)(x);由甲甶elseres=l_mathop(log)(x)/l_mathop(log)(base);由甲男}由甲甸lua_pushnumber(L,res);由甲甹return1;由申田}由申由由申甲#ifdefined(LUA_COMPAT_LOG10)由申申staticintmath_log10(lua_State*L){由申甴lua_pushnumber(L,l_mathop(log10)(luaL_checknumber(L,1)));由申电return1;3C语言的最新标准C11中增加了Generic关键字以支持泛型表达式[1],可以更好的解决这个问题.
不过,Lua的实现尽量避免使用C标准中的太多特性,以提高可移植性.
由由甴第八章内置库的实现由申甶}由申男#endif这里可以看出界畵畡对畁畐畉的锤炼,以及对宿主语言畃语言的逐步脱离.
早期的版本中,是有畭畡畴畨甮畬畯畧和畭畡畴畨甮畬畯畧由田两个畁畐畉的.
目前畬畯畧由田这个版本仅仅考虑兼容因素时才存在.
这缘于畃语言中也有畬畯畧由田的畁畐畉.
但从语义上来看,只需要一个畬畯畧函数就够了4.
早期的界畵畡函数看起来更像是对畃函数的直接映射、而这些年界畵畡正向独立语言而演变,在更高的层面设计畁畐畉就不必再表达实现层面的差别了.
这一小节最后一段值得一读的是畭畡畴畨甮畲畡畮畤畯畭的实现:源代码甸甮男町畬畭畡畴畨畬畩畢甮畣町畲畡畮畤畯畭由甹甸staticintmath_random(lua_State*L){由甹甹/*the'%'avoidsthe(rare)caseofr==1,andisneededalsobecauseon甲田田somesystems(SunOS!
)'rand()'mayreturnavaluelargerthanRAND_MAX*/甲田由lua_Numberr=(lua_Number)(rand()%RAND_MAX)/(lua_Number)RAND_MAX;甲田甲switch(lua_gettop(L)){/*checknumberofarguments*/甲田申case0:{/*noarguments*/甲田甴lua_pushnumber(L,r);/*Numberbetween0and1*/甲田电break;甲田甶}甲田男case1:{/*onlyupperlimit*/甲田甸lua_Numberu=luaL_checknumber(L,1);甲田甹luaL_argcheck(L,(lua_Number)1.
0=0)return(size_t)pos;甴甸elseif(0u-(size_t)pos>len)return0;甴甹elsereturnlen-((size_t)-pos)+1;电田}畬畳畴畲畬畩畢甮畣中间的数百行代码大体分为三个部分.
第一部分,是一些简单的畁畐畉实现,如畳畴畲畩畮畧甮畬略畮、畳畴畲畩畮畧甮畲略當略畲畳略、畳畴畲畩畮畧甮畬畯畷略畲、畳畴畲畩畮畧甮畵異異略畲等等,实现的中规中矩,乏善可陈.
畳畴畲畢畹畴略函数的实现中,有一行畬畵畡界畣畨略畣畫畳畴畡畣畫调用值得初学界畵畡的畃畢畩畮畤畩畮畧畳编写人员注意.
界畵畡的栈不像畃语言的栈那样,不大考虑栈溢出的情况.
界畵畡栈给畃函数留的默认空间很小,默认情况下只有甲田8.
当你要在界畵畡的栈上留下大量值时,务必用畬畵畡界畣畨略畣畫畳畴畡畣畫扩展堆栈.
因为处于性能考虑,界畵畡和栈有关的畁畐畉都是不检查栈溢出的情况的.
源代码甸甮由甲町畬畳畴畲畬畩畢甮畣町畢畹畴略由甴由if(posi>pose)return0;/*emptyinterval;returnnovalues*/由甴甲n=(int)(pose-posi+1);由甴申if(posi+nint)overflow*/由甴甴returnluaL_error(L,"stringslicetoolong");由甴电luaL_checkstack(L,n,"stringslicetoolong");暂时不继续写这一章了.
8.
4暂且搁置7C语言的char类型是singed还是unsigned依赖于实现[2].
我们常用的C编译器如gcc的char类型是有符号的,也有一些C编译器如WatcomC的char类型默认则是无符号的.
8LUAMINSTACK定义在lua.
h中,默认值为20.
参考文献畛由畝畉畳畯甯畩略畣甹甸甹甹町甲田由畸甮畉畮Programminglanguages-C甬畣畨畡異畴略畲甶甮电甮由由畇略畮略畲畩畣畳略畬略畣畴畩畯畮甮畛甲畝畉畳畯甯畩略畣甹甸甹甹町甲田由畸甮畉畮Programminglanguages-C甬畣畨畡異畴略畲甶甮甲甮电畔畹異略畳甮畛申畝畒畯畢略畲畴畯畉略畲畵畳畡畬畩畭畳畣畨畹甮畔畨略畮畯當略畬畴畩略畳畯畦畬畵畡电甮甲甮甲田由由甮http://www.
inf.
puc-rio.
br/~roberto/talks/novelties-5.
2.
pdf甮畛甴畝畗畡畬畤略畭畡畲畃略畬略畳畒畯畢略畲畴畯畉略畲畵畳畡畬畩畭畳畣畨畹甬界畵畩畺畈略畮畲畩畱畵略畤略畆畩畧畵略畩畲略畤畯甮畔畨略略當畯畬畵畴畩畯畮畯畦畬畵畡甮ProceedingsofACMHOPLIII(2007)2-1-2-26甮http://www.
lua.
org/doc/hopl.
pdf甮畛电畝畗畡畬畤略畭畡畲畃略畬略畳畒畯畢略畲畴畯畉略畲畵畳畡畬畩畭畳畣畨畹甬界畵畩畺畈略畮畲畩畱畵略畤略畆畩畧畵略畩畲略畤畯甮Lua5.
2ReferenceManual甬甲田由由甮http://www.
lua.
org/manual/5.
2/manual.
html甮畛甶畝界畵畡畗畩畫畩甮畈畡畳畨畤畯畳甮http://lua-users.
org/wiki/HashDos甮畛男畝畗畩畫畩異略畤畩畡甮畂畬畯畣畫畳用畣畬畡畮畧畵畡畧略略畸畴略畮畳畩畯畮甩甮http://en.
wikipedia.
org/wiki/Blocks_(C_language_extension)甮畛甸畝畗畩畫畩異略畤畩畡甮畉畮畤略畮畴畳畴畹畬略甮http://en.
wikipedia.
org/wiki/Indent_style甮畛甹畝畗畩畫畩異略畤畩畡甮畎畡畮甮http://en.
wikipedia.
org/wiki/NaN甮畛由田畝畗畩畫畩異略畤畩畡甮畓略異畡畲畡畴略畣畨畡畩畮畩畮畧甮http://en.
wikipedia.
org/wiki/Hash_table#Separate_chaining甮由由男由由甸参考文献源代码目录甲甮由畬畡畵畸畬畩畢甮畣町畬畵畡界畮略畷畳畴畡畴略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甲甮甲畬畭略畭甮畨町畭略畭畯畲畹甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甲甮申畬畭略畭甮畣町畲略畡畬畬畯畣甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甲甮甴畬畭略畭甮畣町畧畲畯畷當略畣畴畯畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甲甮电畬畳畴畡畴略甮畣町界畇甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甲甮甶畬畳畴畡畴略甮畣町畬畵畡畮略畷畳畴畡畴略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甲甮男畬畡異畩甮畣町畬畵畡當略畲畳畩畯畮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由申甲甮甸畬畡畵畸畬畩畢甮畣町畬畵畡界畣畨略畣畫當略畲畳畩畯畮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由申甲甮甹畬畳畴畡畴略甮畣町畦畬畵畡畯異略畮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甴申甮由畬畯畢番略畣畴甮畨町當畡畲畩畡畮畴畳畴畲畩畮畧甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由男申甮甲畬畯畢番略畣畴甮畨町畴畳畴畲畩畮畧甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由男申甮申畬畯畢番略畣畴甮畨町畧略畴畳畴畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甸申甮甴畬畳畴畡畴略甮畨町畳畴畲畩畮畧畴畡畢畬略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甸申甮电畬畳畴畲畩畮畧甮畣町畳畴畲畩畮畧畨畡畳畨甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甹申甮甶畬畳畴畡畴略甮畣町畭畡畫略畳略略畤甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由甹申甮男畬畳畴畲畩畮畧甮畣町略畱畳畴畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲田申甮甸畬畳畴畲畩畮畧甮畣町略畱畬畮畧畳畴畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲田申甮甹畬畳畴畲畩畮畧甮畨町略畱畳畨畲畳畴畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲田申甮由田畬畳畴畲畩畮畧甮畣町畩畮畴略畲畮畳畨畲畳畴畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲田申甮由由畬畳畴畲畩畮畧甮畣町畲略畳畩畺略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲由申甮由甲畬畳畴畲畩畮畧甮畣町畣畲略畡畴略畳畴畲畯畢番甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲甲申甮由申畬畯畢番略畣畴甮畨町畵畤畡畴畡甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲甲申甮由甴畬畳畴畲畩畮畧甮畣町畮略畷畵畤畡畴畡甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲申甴甮由畬畯畢番略畣畴甮畨町畴畡畢畬略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲电甴甮甲畬畴畡畢畬略甮畣町畤畵畭畭畹畮畯畤略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲甶甴甮申畬畴畡畢畬略甮畣町畮略畷甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲男甴甮甴畬畴畡畢畬略甮畣町畮略畷畫略畹甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲男甴甮电畬畴畡畢畬略甮畣町畲略畨畡畳畨甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甲甹甴甮甶畬畴畡畢畬略甮畣町畭畡畩畮異畯畳畩畴畩畯畮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申田甴甮男畬畴畡畢畬略甮畣町畧略畴甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申田甴甮甸畬畴畡畢畬略甮畣町畨畡畳畨畮畵畭甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申甲甴甮甹畬畬畩畭畩畴畳甮畨町畨畡畳畨畮畵畭由甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申甲甴甮由田畬畬畩畭畩畴畳甮畨町畨畡畳畨畮畵畭甲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申申由由甹由甲田源代码目录甴甮由由畬畴畡畢畬略甮畣町畮略畸畴甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申申甴甮由甲畬畴畡畢畬略甮畣町甌畮畤畩畮畤略畸甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申甴甴甮由申畬畴畡畢畬略甮畣町畧略畴畮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申电甴甮由甴畬畴畭甮畨町畦畡畳畴畴畭甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申甶甴甮由电畬畴畭甮畣町畩畮畩畴甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申甶甴甮由甶畬畴畭甮畣町畧略畴畴畭甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申男甴甮由男畬畴畭甮畣町畴畹異略畮畡畭略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮申男电甮由畬畯畢番略畣畴甮畨町畐畲畯畴畯甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴田电甮甲畬畦畵畮畣甮畣町畬畵畡畆畦畲略略異畲畯畴畯甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴由电甮申畬畯畢番略畣畴甮畨町畕異畖畡畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甲电甮甴畬畯畢番略畣畴甮畨町界畃畬畯畳畵畲略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴申电甮电畬畦畵畮畣甮畣町畬畵畡畆畮略畷界畣畬畯畳畵畲略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴申电甮甶畬畦畵畮畣甮畣町畬畵畡畆甌畮畤畵異當畡畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴申电甮男畬畦畵畮畣甮畣町畬畵畡畆畣畬畯畳略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甴电甮甸畬畤畯甮畣町畦異畡畲畳略畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴电电甮甹畬畦畵畮畣甮畣町畬畵畡畆畮略畷畵異當畡畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甶电甮由田畬畯畢番略畣畴甮畨町畃畃畬畯畳畵畲略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甶电甮由由畬畦畵畮畣甮畣町畬畵畡畆畮略畷畃畣畬畯畳畵畲略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴甶电甮由甲畬畡異畩甮畣町畬畵畡異畵畳畨畣畣畬畯畳畵畲略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴男电甮由申畬畯畢番略畣畴甮畨町畬畵畡畔畆畕畎畃畔畉畏畎甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甴男甶甮由畬畳畴畡畴略甮畨町畬畵畡畓畴畡畴略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电田甶甮甲畬畯畢番略畣畴甮畨町畖畡畬畵略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电田甶甮申畬畳畴畡畴略甮畣町畳畴畡畣畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电由甶甮甴畬畤畯甮畣町畧畲畯畷畳畴畡畣畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甲甶甮电畬畤畯甮畣町畣畯畲畲略畣畴畳畴畡畣畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电申甶甮甶畬畳畴畡畴略甮畨町畃畡畬畬畉畮畦畯甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甴甶甮男畬畳畴畡畴略甮畣町畬畵畡畅略畸畴略畮畤畃畉甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甶甶甮甸畬畤畯甮畣町畮略畸畴畣畩甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甶甶甮甹畬畳畴畡畴略甮畣町畬畵畡畅畦畲略略畃畉甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甶甶甮由田畬畳畴畡畴略甮畣町畬畵畡畮略畷畴畨畲略畡畤甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电男甶甮由由畬畳畴畡畴略甮畣町界畘甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电男甶甮由甲畬畤畯甮畣町界畕畁畉畔畈畒畏畗甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甸甶甮由申畬畤畯甮畣町畬畵畡畬畯畮畧番畭異甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甹甶甮由甴畬畤畯甮畣町畬畵畡畄畲畡畷畲畵畮異畲畯畴略畣畴略畤甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甹甶甮由电畬畤畯甮畣町畬畵畡畄畴畨畲畯畷甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮电甹甶甮由甶畬畤畯甮畣町畬畵畡畄異畣畡畬畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶田甶甮由男畬畤畯甮畨町畲略畳畴畯畲略畳畴畡畣畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶由甶甮由甸畬畤畯甮畣町畬畵畡畄異畲略畣畡畬畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶由甶甮由甹畬畤畯甮畣町畡畤番畵畳畴當畡畲畡畲畧畳甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶申甶甮甲田畬畤畯甮畣町畴畲畹畦畵畮畣畔畍甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶甴甶甮甲由畬畤畯甮畣町畬畵畡畄異畯畳畣畡畬畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶甴源代码目录由甲由甶甮甲甲畬畤畯甮畣町畬畵畡畄畣畡畬畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶电甶甮甲申畬畤畯甮畣町畬畵畡畄畨畯畯畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶甶甶甮甲甴畬畤畯甮畣町畬畵畡畹畩略畬畤畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶甸甶甮甲电畬畤畯甮畣町畬畵畡畲略畳畵畭略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甶甹甶甮甲甶畬畤畯甮畣町畲略畳畵畭略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男田甶甮甲男畬畤畯甮畣町畵畮畲畯畬畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男由甶甮甲甸畬畤畯甮畣町甌畮畩畳畨畃畣畡畬畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男由甶甮甲甹畬畤畯甮畣町畲略畣畯當略畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甲甶甮申田畬畡異畩甮畣町畬畵畡畣畡畬畬畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男申甶甮申由畬畡異畩甮畣町畬畵畡異畣畡畬畬畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甴甶甮申甲畬畤略畢畵畧甮畣町畬畵畡畇略畲畲畯畲畭畳畧甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甶甶甮申申畬畡異畩甮畣町畬畵畡略畲畲畯畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甶男甮由畬畯異畣畯畤略畳甮畨町畯異畣畯畤略畡畲畧甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甸男甮甲畬畯異畣畯畤略畳甮畨町畲畫甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮男甹男甮申畬當畭甮畣町畬畵畡畖略畸略畣畵畴略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甲男甮甴畬當畭甮畣町畐畲畯畴略畣畴甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸申男甮电畬當畭甮畣町當畭畤畩畳異畡畴畣畨甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸申男甮甶畬當畭甮畣町畏畐畍畏畖畅甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸申男甮男畬當畭甮畣町畏畐界畏畁畄畎畉界畏畐界畏畁畄畂畏畏界甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甴男甮甸畬當畭甮畣町畏畐界畏畁畄畋甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甴男甮甹畬當畭甮畣町畏畐畇畅畔畕畐畖畁界甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸电男甮由田畬當畭甮畣町畬畵畡畖畧略畴畴畡畢畬略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甶男甮由由畬當畭甮畣町畣畡畬畬畔畍甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸男男甮由甲畬當畭甮畣町畬畵畡畖畳略畴畴畡畢畬略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甸男甮由申畬當畭甮畣町略畸異畲略畳畳畩畯畮畳甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甸甹男甮由甴畬當畭甮畣町畡畲畩畴畨畯異甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹田男甮由电畬當畭甮畣町畬畵畡畖畡畲畩畴畨甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹由男甮由甶畬當畭甮畣町畣畡畬畬畢畩畮畔畍甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹由男甮由男畬當畭甮畣町畣畡畬畬畢畩畮畔畍甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甲男甮由甸畬當畭甮畣町畬畵畡畖畣畯畮畣畡畴甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹申男甮由甹畬當畭甮畣町畬畵畡畖畴畯畳畴畲畩畮畧甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甴男甮甲田畬當畭甮畣町畤畯番畵畭異甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹电男甮甲由畬當畭甮畣町畏畐畊畍畐甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹电男甮甲甲畬當畭甮畣町畏畐畅畑甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹电男甮甲申畬當畭甮畨町略畱畵畡畬畯畢番甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甶男甮甲甴畬當畭甮畣町畬畵畡畖略畱畵畡畬畯畢番甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甶男甮甲电畬當畭甮畣町畧略畴略畱畵畡畬畔畍甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹男男甮甲甶畬當畭甮畨町畬畵畡畖畲畡畷略畱畵畡畬畯畢番甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甸男甮甲男畬當畭甮畣町畬畵畡畖畬略畳畳畴畨畡畮畬畵畡畖畬略畳畳略畱畵畡畬甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甸男甮甲甸畬當畭甮畣町畬畳畴畲畣畭異甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甹男甮甲甹畬當畭甮畣町畏畐畃畁界界甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甹甹由甲甲源代码目录男甮申田畬當畭甮畣町畏畐畔畁畉界畃畁界界甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田田男甮申由畬當畭甮畣町畏畐畖畁畒畁畒畇甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甲男甮申甲畬當畭甮畣町畏畐畓畅畔界畉畓畔甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田申男甮申申畬當畭甮畣町畏畐畃界畏畓畕畒畅甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甴男甮申甴畬當畭甮畣町畧略畴畣畡畣畨略畤甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甴男甮申电畬畯畢番略畣畴甮畨町畕異當畡畬畤略畳畣甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田电男甮申甶畬當畭甮畣町異畵畳畨畣畬畯畳畵畲略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田电男甮申男畬當畭甮畣町畆畏畒畐畒畅畐甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甶男甮申甸畬當畭甮畣町畆畏畒界畏畏畐甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田男男甮申甹畬當畭甮畣町畔畆畏畒畃畁界界畔畆畏畒界畏畏畐甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甸男甮甴田畬當畭甮畣町畬畵畡畖甌畮畩畳畨畏異甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由田甸甸甮由畬畭畡畴畨畬畩畢甮畣町畭畡畴畨畬畩畢甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由由甸甮甲畬畭畡畴畨畬畩畢甮畣町畬畵畡畯異略畮畭畡畴畨甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由由甸甮申畬畡畵畸畬畩畢甮畨町畬畵畡界畮略畷畬畩畢甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甲甸甮甴畬畡畵畸畬畩畢甮畣町畬畵畡界畳略畴畦畵畮畣畳甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甲甸甮电畬畵畡畣畯畮畦甮畨町畬畭畡畴畨畯異甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由申甸甮甶畬畭畡畴畨畬畩畢甮畣町畬畯畧甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由申甸甮男畬畭畡畴畨畬畩畢甮畣町畲畡畮畤畯畭甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甴甸甮甸畬畳畴畲畬畩畢甮畣町畯異略畮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由电甸甮甹畬畳畴畲畬畩畢甮畣町畣畲略畡畴略畭略畴畡畴畡畢畬略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由电甸甮由田畬畳畴畲畬畩畢甮畣町畵畣畨畡畲甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甶甸甮由由畬畳畴畲畬畩畢甮畣町異畯畳畲略畬畡畴甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甶甸甮由甲畬畳畴畲畬畩畢甮畣町畢畹畴略甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮甮由由甶

WHloud Date鲸云数据($9.00/月), 韩国,日本,香港

WHloud Date(鲸云数据),原做大数据和软件开发的团队,现在转变成云计算服务,面对海内外用户提供中国大陆,韩国,日本,香港等多个地方节点服务。24*7小时的在线支持,较为全面的虚拟化构架以及全方面的技术支持!官方网站:https://www.whloud.com/WHloud Date 韩国BGP云主机少量补货随时可以开通,随时可以用,两小时内提交退款,可在工作日期间全额原路返回!支持pa...

无忧云( 9.9元/首月),河南洛阳BGP 2核 2G,大连BGP线路 20G高防 ,

无忧云怎么样?无忧云服务器好不好?无忧云值不值得购买?无忧云,无忧云是一家成立于2017年的老牌商家旗下的服务器销售品牌,现由深圳市云上无忧网络科技有限公司运营,是正规持证IDC/ISP/IRCS商家,自营有国内雅安高防、洛阳BGP企业线路、香港CN2线路、国外服务器产品等,非常适合需要稳定的线路的用户,如游戏、企业建站业务需求和各种负载较高的项目,同时还有自营的高性能、高配置的BGP线路高防物理...

远程登录VNC无法连接出现

今天有网友提到自己在Linux服务器中安装VNC桌面的时候安装都没有问题,但是在登录远程的时候居然有出现灰色界面,有三行代码提示"Accept clipboard from viewers,Send clipboard to viewers,Send primary selection to viewers"。即便我们重新登录也不行,这个到底如何解决呢?这里找几个可以解决的可能办法,我们多多尝试。...

wordpress模板为你推荐
设置route0.001csswordpress模板wordpress高手进,我是新手,不知道下载的模板应该放在wordpress的那个地方.请高手指点.谢谢wordpress模板wordpress后台默认模板管理在哪里?verticalflash资费标准中国电信套餐资费一览表2021即时通请问有没有人知道即时通是什么?怎样先可以开??网站方案设计网站文案策划怎么写团购程序团购的具体流程是什么?仿佛很简单便捷的样子?kingcmsKingCMS 开始该则呢么设置呢?
香港服务器租用99idc 动态ip的vps 域名服务dns的主要功能为 查询ip地址 草根过期域名 主机优惠码 名片模板psd 监控宝 免费博客空间 免费静态空间 空间出租 idc资讯 699美元 新家坡 中国电信测网速 1g内存 网络空间租赁 服务器合租 卡巴斯基免费试用版 drupal安装 更多