Lua是 一门以高效著称的脚本语言,为了达到较高的执行效率,Lua从1.0(1993年发布)开始就内置了虚拟机lvm。也就是说,Lua脚本并不是直接被Lua解释器解释执行的,而是类似于Java那样,先由Lua编译器编译为字节码ByteCode,然后交由Lua虚拟机去执行。Lua字节码ByteCode需要一个载体,这个载体就是二进制chunk,可以将Lua的二进制chunk看做Java的class文件。

chunk

在Lua中一段可以被Lua解释器解释执行的代码叫做chunkchunk可以很小,小到一两条语句。也可以很大,大到包含成千上万语句和复杂的函数定义。为了获得较高的执行效率,Lua并不直接解释执行chunk,而是先由编译器编译成内部结构,其中包含字节码等信息,然后再由虚拟机执行字节码。这种内部结构在Lua里叫做预编译(Precompiled)chunk,由于采用了二进制格式,所以也叫做二进制(Binary)chunk

隐式调用Lua编译器

Lua程序员一般无需关心二进制chunk,因为Lua解释器会在内部进行编译。Lua提供了命令行工具luac,可以把Lua源代码编译成二进制chunk,并保存成文件,默认文件名为luac.out。Lua解释器可直接加载并执行二进制chunk文件。

显式调用Lua解释器

Lua解释器会在内部编译Lua脚本,所以预编译并不会加快脚本执行的速度,但是预编译可以加快脚本加载的速度,并可以在一定程序上保护源代码。另外luac还提供了反编译功能,方便查看二进制chunk内容和Lua虚拟机指令。

luac

luac命令主要由2个用途:

  1. 作为编译器,把Lua文件编译成二进制chunk文件
  2. 作为反编译器,分析二进制chunk,将信息输出到控制台。

Lua将编译命令和反编译命令整合在一起,在命令行直接执行luac命令可查看其完整用法。

λ luac
luac: no input files given
usage: luac [options] [filenames].
Available options are:-        process stdin-l       list 查看二进制chunk-o name  output to file 'name' (default is "luac.out") 对输出文件进行明确指定-p       parse only 仅执行解释,即只是检查语法是否正确,不产生输出文件。-s       strip debug information 去掉编译生成的二进制chunk默认包含的调试信息(行号、变量名等)-v       show version information 显示版本信息--       stop handling options$ luac test.lua # 生成luac.out
$ luac test.lua -o test.out # 生成test.out
$ luac test.lua -s # 不包含调试信息
$ luac test.lua -p # 只进行语法检查

编译Lua源代码

将一个或多个文件名作为参数调用luac命令就可以编译指定的Lua源文件,若编译成功则在当前目录下生成luac.out文件,其中的内容就是对应的二进制chunk

Lua编译器工作原理

Lua编译器以函数为单位进行编译,每个函数都会被Lua编译器编译称为一个内部结构,这个结构叫做“原型”(prototype),原型主要包含6部分内容,分别是:

  1. 函数基本信息:包含参数数量、局部变量数量等
  2. 字节码
  3. 常量表
  4. Upvalue表
  5. 调试信息
  6. 子函数原型列表

由此可知,函数原型是一种递归结构,Lua源码中函数的嵌套关系会直接反映在编译后的原型中。

print("hello, world!")

上面仅有一条打印语句,并没有定义函数,那么Lua编译器是怎么编译这个文件的呢?由于Lua是脚本语言,如果每执行一段脚本都必须要定义一个函数,不是很麻烦吗?所以这样吃力不讨好的工作就由Lua编译器代劳了。

Lua编译器会自动为脚本添加一个main主函数,并将整个程序都放在这个函数里,然后再以它为起点进行编译,那么自然就把整个程序都编译出来了。主函数不仅仅是编译的起点,也是为了Lua虚拟机解释执行程序时的入口。

程序被Lua编译器加工后,会变成如下:

function main(...)print("hello world!")  return
end

将主函数编译成函数原型后,Lua编译器会给它再添加一个头部Header,然后一起dump成为luac.out文件,这样二进制chunk文件就产生了。

二进制chunk内部结构

查看二进制chunk

二进制chunk之所以使用二进制格式,是为了方便虚拟机加载,然后对人类却不够友好,因为其很难直接阅读。luac命令兼具编译和反编译功能,使用luac -l选项可查看二进制chunk,即luac反编译器精简模式的输出类型。

$ vim test.lua
-- test.lua
print("hello world")
$ luac test.lua
$ luac -l luac.out
main <test.lua:0,0> (4 instructions, 16 bytes at 006BECA0)
0+ params, 2 slots, 0 upvalues, 0 locals, 2 constants, 0 functions1       [2]     GETGLOBAL       0 -1    ; print2       [2]     LOADK           1 -2    ; "hello world"3       [2]     CALL            0 2 14       [2]     RETURN          0 1
$ vim test.lua
function foo()function bar()end
end
$ luac test.lua
$ luac -l test.out
main <test.lua:0,0> (3 instructions, 12 bytes at 0206ECA0)
0+ params, 2 slots, 0 upvalues, 0 locals, 1 constant, 1 function1       [4]     CLOSURE         0 0     ; 0206EE102       [1]     SETGLOBAL       0 -1    ; foo3       [4]     RETURN          0 1function <test.lua:1,4> (3 instructions, 12 bytes at 0206EE10)
0 params, 2 slots, 0 upvalues, 0 locals, 1 constant, 1 function1       [3]     CLOSURE         0 0     ; 0206EE782       [2]     SETGLOBAL       0 -1    ; bar3       [4]     RETURN          0 1function <test.lua:2,3> (1 instruction, 4 bytes at 0206EE78)
0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions1       [3]     RETURN          0 1

使用luac -l反编译打印的函数信息包含两个部分:前两行是函数基本信息,后面是指令列表

main <test.lua:0,0> (4 instructions, 16 bytes at 006BECA0)function <test.lua:1,4> (3 instructions, 12 bytes at 0206EE10)函数 <源文件名:起始行号,终止行号>(指令数量, 函数地址)

第1行:若以main开头说明编译器自动生成主函数,若以function开头则说明是一个普通函数。接着是定义函数的源文件名和函数在文件里的起止行号(对于主函数,起止行号都是0),然后是指令数量和函数地址。

第2行:依次给出函数的固定参数数量(若有+号表示是一个vararg函数)、运行函数所必要的寄存器数量、upvalue数量、局部变量数量、常量数量、子函数数量。

0+ params, 2 slots, 0 upvalues, 0 locals, 1 constant, 1 function
参数数量, 寄存器数量, upvalue数量, 局部变量数量, 常量数量, 子函数数量

指令列表中每条指令都包含指令序号、对应行号、操作码、操作数。分号后面是luac根据指令操作数生成的注释,以便于理解指令。

指令序号, 对应行号, 操作码, 操作数, 注释
1       [4]     CLOSURE         0 0     ; 0206EE10
2       [1]     SETGLOBAL       0 -1    ; foo
3       [4]     RETURN          0 1

luac反编译器详细模式输出内容,luac会将常量表、局部变量表、upvalue表信息也打印出来。

$ luac -l -l luac.outmain <test.lua:0,0> (3 instructions, 12 bytes at 01FBECA0)
0+ params, 2 slots, 0 upvalues, 0 locals, 1 constant, 1 function1       [4]     CLOSURE         0 0     ; 01FBEE282       [1]     SETGLOBAL       0 -1    ; foo3       [4]     RETURN          0 1
constants (1) for 01FBECA0:1       "foo"
locals (0) for 01FBECA0:
upvalues (0) for 01FBECA0:function <test.lua:1,4> (3 instructions, 12 bytes at 01FBEE28)
0 params, 2 slots, 0 upvalues, 0 locals, 1 constant, 1 function1       [3]     CLOSURE         0 0     ; 01FBEE902       [2]     SETGLOBAL       0 -1    ; bar3       [4]     RETURN          0 1
constants (1) for 01FBEE28:1       "bar"
locals (0) for 01FBEE28:
upvalues (0) for 01FBEE28:function <test.lua:2,3> (1 instruction, 4 bytes at 01FBEE90)
0 params, 2 slots, 0 upvalues, 0 locals, 0 constants, 0 functions1       [3]     RETURN          0 1
constants (0) for 01FBEE90:
locals (0) for 01FBEE90:
upvalues (0) for 01FBEE90:

chunk格式

Lua的二进制chunk本质上也是一个字节流

  1. 二进制chunk格式属于Lua虚拟机内部实现细节,并未标准化,也没有官方文档说明,一切以Lua官方实现的源码为准。
  2. 二进制chunk格式的设计没有考虑跨平台的需求,对于需要使用一个字节表示的数据,必须要考虑大小端(Endianness)问题。Lua官方实现的做法是编译Lua脚本时,直接按照本机的大小端方式生成二进制chunk文件,当加载二进制chunk文件时,会探测被加载文件的大小端方式,如果和本机不匹配就拒绝加载。
  3. 二进制chunk格式的设计没有考虑Lua版本兼容性,Lua官方做法是编译Lua脚本时,直接按照当时的Lua版本生成chunk文件,当加载二进制chunk文件时,会检测被加载文件的版本号,如果和当前Lua版本不一致则拒绝加载。
  4. 二进制chunk格式的设计没有刻意设计的很紧凑,在某些情况下一段Lua脚本被编译成二进制chunk后甚至会比文本形式的源文件还要大。由于把Lua脚本编译成二进制chunk的主要目的是为了获得更快的加载速度,所以这也不是什么大问题。

数据类型

二进制chunk本质上来说是一个字节流,一个字节能够表示的信息是非常有限的,如一个ASCII码或一个很小的整数可以放进一个字节内,但是更复杂的信息就必须通过某种编码方式编码成多个字节。在讨论二进制chunk格式时,称这种被编码为一个或多个字节的信息单位为数据类型。

由于Lua官方实现是使用C语言编写的,所以C语言的一些数据类型会直接反映在二进制chunk的格式里。二进制chunk内部使用的数据类型大致分为数字、字符串、列表三种。

  1. 数字

数字类型主要包括5种:

  • 字节:用来存放一些比较小的整数值,比如Lua版本号、函数的参数个数等。
  • C语言cint整型:主要用来表示列表长度
  • C语言size_t类型:主要用来表示长字符串长度
  • Lua整数:Lua整数和Lua浮点数则主要在常量表里出现,记录Lua脚本中出现的整数和浮点数字面量。
  • Lua浮点数

数字类型在二进制chunk里都按照固定长度存储,除字节类型外,其余4种数字类型都会占用多个字节,具体占用字节数则会及记录在头部中。

二进制chunk整数类型
  1. 字符串

字符串在二进制chunk中其实是一个字节数组,因为字符串长度不固定,所以需要将字节数组的长度也记录到二进制chunk中。作为优化,字符串类型又可以进一步分为短字符串和长字符串,具体有3种情况:

  • 对于NULL字符串只用0x00表示即可
  • 对于长度小于等于2530xFD的字符串,先使用一个字节记录长度+1,然后是字节数组。
  • 对于长度大于等于254oxFE的字符串,第一个字节是oxFF,其后是size_t记录长度+1,最后是字节数组。
字符串存储格式
  1. 列表

在二进制chunk内部,指令表、常量表、子函数原型表等信息都按照列表的方式存储。即先用一个cint类型记录列表长度,然后紧接着存储n个列表元素,至于列表元素如何存储需具体情况具体分析。

Lua二进制chunk相关推荐

  1. Lua 5.1 参考手册

    Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes 云风 译 www.codingno ...

  2. 深入理解 Lua 虚拟机

    作者:nicochen,腾讯 IEG 游戏开发工程师 本文从一个简单示例入手,详细讲解 Lua 字节码文件的存储结构及各字段含义,进而引出 Lua 虚拟机指令集和运行时的核心数据结构 Lua Stat ...

  3. lua 获取网络时间_Lua脚本引擎教程:学习路线

    从零开始的Lua教程(零):学习路线 出于分享学习心得和获得同伴讨论的目的,我打算写一个教程给想要学习如何实现lua解释器的同学提供一个参考. 我的学习经历 我在去年4月开始接触到游戏引擎的脚本引擎, ...

  4. Cocos2d-x游戏开发之Lua 5.1 参考手册

    Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de Figueiredo, Waldemar Celes 云风 译 www.codingno ...

  5. 【Lua】Lua知识点汇总

    Lua知识点汇总 一.理解Lua的执行 二.Lua编译器 2.1 词法分析器 2.2 抽象语法树 2.3 语法分析 2.4 代码生成 三.Lua解析器 3.1 luac命令 3.2 二进制chunk格 ...

  6. Golang 和 lua 相遇会擦出什么火花?

    在 GitHub 玩耍时,偶然发现了 gopher-lua ,这是一个纯 Golang 实现的 Lua 虚拟机.我们知道 Golang 是静态语言,而 Lua 是动态语言,Golang 的性能和效率各 ...

  7. Go语言和php个和lua,当 Go 遇上了 Lua

    在 GitHub 玩耍时,偶然发现了 gopher-lua ,这是一个纯 Golang 实现的 Lua 虚拟机.我们知道 Golang 是静态语言,而 Lua 是动态语言,Golang 的性能和效率各 ...

  8. Go语言和php个和lua,当Go遇上了Lua,会发生什么

    在 GitHub 玩耍时,偶然发现了 gopher-lua ,这是一个纯 Golang 实现的 Lua 虚拟机.我们知道 Golang 是静态语言,而 Lua 是动态语言,Golang 的性能和效率各 ...

  9. Lua5.1中的API函数

    lua_State* luaL_newstate() Lua脚本的编译执行是相互独立的,在不同的线程上执行.通过luaL_newstate()函数可以申请一个虚拟机,返回指针类型 lua_State. ...

  10. skynet 游戏服务器探索(1)--熟悉skynet(网络)

    因为做游戏服务器开发,大多数都跟脚本打交道,要么是lua,要么是python,要么是php,方便热更新的只有lua与php, php相关的游戏服务器开发,参考我另外的文章 https://blog.c ...

最新文章

  1. Scrapy_redis框架原理分析并实现断点续爬以及分布式爬虫
  2. linux smart服务,RAKsmart Linux美国服务器常用信息命令
  3. c++ 多重背包状态转移方程_【模板】各种背包问题amp;讲解
  4. 循环体内,字符串的连接方式,使用StringBuilder的append方法进行扩展
  5. 《白鹿原》金句摘抄(三)
  6. linux操作系统好吗_国内可以通过安卓+termux打造出适用手机平板和电脑全平台最好的操作系统...
  7. 发票管理软件_企业为什么需要ERP企业管理软件?
  8. python数据框循环生成_python - 如何在 Pandas 的for循环迭代中创建多个数据框?
  9. linux杀死进程删除文件,linux 常用到的命令 删除 移动 复制 查询端口 杀死进程 查询进程...
  10. PHP导航猫网址导航系统源码V2.4.5
  11. ubuntu高版本环境变量问题
  12. 负载均衡的原理和架构
  13. GitHub上下载源代码的方法
  14. 计算机网络网络应用之P2P应用
  15. jsp中嵌入java代码实例,jsp中嵌入java代码
  16. JAVA看云判断天气_看云,能否“识”天气?
  17. Java中 … 三个点是什么意思?
  18. NOTION 换深色背景 黑色背景
  19. 达芬奇密码 第三十章
  20. wifi底层学习之路:四,iw指令剖析

热门文章

  1. 东北师范大学计算机信息技术学院,东北师范大学计算机科学与信息技术学院2015年硕士研究生招生专业目录...
  2. Linux之常见面试题知识点批注(六)
  3. 初级系列11.个人所得税问题
  4. 怎么修改Word每行文字间距
  5. 广工Libero SoC安装教程
  6. 20201220苹果黑解开放-黑解教程 最新iccid
  7. java 用户名称中emoji表情包的模糊处理
  8. 从零开始,耗时两年,19岁小伙自制一块32位Risc-V处理器,可玩「贪吃蛇」
  9. 精神小伙python小白用python可视化分析青春有你2小姐姐有关数据
  10. 钢琴节奏时值测试软件,钢琴技巧:弹奏时值较长双音的技巧——自网络