正如之前所提到的, Lua
语言会在运行源代码之前先对其进行预编译。 Lua
语言也允许我们以预编译的形式分发代码。
生成预编译文件(也被称为二进制文件)的最简单方式是,使用标准发行版中附带的 luac
程序,例如,下面命令会创建文件 prog.lua
的预编译版本 prog.lc
:
$ luac -o prog.lc prog.lua
Lua
解析器会像执行普通 Lua
代码一样执行这个新文件,完成与原来代码完全一致的动作:
$ lua prog.lc
几乎在 Lua
语言中所有能够使用源码的地方都可以使用预编译代码。特别地,函数 loadfile
和函数 load
都可以接受预编译代码。
我们可以直接在 Lua
语言中实现一个最简单的 luac
:
p = loadfile(arg[1]) f = io.open(arg[2], "wb") f:write(string.dump(p)) f:close()
这里的关键函数是 string.dump
,该函数的传入参数是一个 Lua
函数,返回值是传入函数对应的字符串形式的预编译代码(已被正确的格式化,可由 Lua
语言直接加载)。
luac
程序提供了一些有意思的选项。特别地,选项 -l
会列出编译器为指定代码生成的操作码( opcode
)。例如,新建一个 main.lua
文件,写入如下内容:
a = x + y - z
执行 luac -l
命令得到输出:
$ luac -l main.lua main <main.lua:0,0> (7 instructions at 0x5560deb9fc60) 0+ params, 2 slots, 1 upvalue, 0 locals, 4 constants, 0 functions 1 [1] GETTABUP 0 0 -2 ; _ENV "x" 2 [1] GETTABUP 1 0 -3 ; _ENV "y" 3 [1] ADD 0 0 1 4 [1] GETTABUP 1 0 -4 ; _ENV "z" 5 [1] SUB 0 0 1 6 [1] SETTABUP 0 -1 0 ; _ENV "a" 7 [1] RETURN 0 1
提示
关于Lua语言内部实现的细节可以查看Lua OpCode笔记。
预编译形式的代码不一定比源代码更小,但是却加载的更快。预编译形式的代码的另一个好处是,可以避免由于意外而修改代码。然而,与源代码不同,蓄意损坏或构造的二进制代码可能会让 Lua
解析器崩溃或甚至执行用户提供的机器码。当运行一般的代码时通常无需担心,但应该避免运行以预编译形式给出的非受信代码。这种需求,函数 load
正好有一个选项可以适用。
除了必需的第一个参数外,函数 load
还有 3
个可选参数。第 2
个参数是程序段的名称,只在错误信息中被适用。第 4
个参数是环境,会在环境部分详细讲解。第 3
个参数正是我们这里所关心的,它控制了允许加载的代码段类型。如果该参数存在,则只能是如下的字符串:
"t"
:允许加载文本(普通)类型的代码段。"b"
:只允许加载二进制(预编译)类型的代码段。"bt"
:允许同时加载文本和二进制类型的代码段——默认情况。