先试下 globals.lua, 这个是从 Lua1.0 那里拷贝过来的。因为在 Lua1.0 里对这个程序的输出有点疑问,所以这里再执行一下它。
源代码如下:
---------------------------
$debug
k,v=nextvar(k)
while k do
print(k)
k,v=nextvar(k)
end
---------------------------
执行一下输出:
type
tonumber
next
nextvar
print
dofile
dostring
readfrom
writeto
appendto
read
write
execute
remove
strfind
strlen
strsub
strlower
strupper
abs
sin
cos
tan
asin
acos
atan
ceil
floor
mod
sqrt
pow
min
max
k
v
可以看到,k, v 都打印出来了,而在 Lua1.0 里只有 k 打印出来了, v 没有。Lua1.0 里应该有问题。
简单的分析下结果,这些打印出来的都是全局变量,包括由 C 注册的和 Lua 中定义的。
type,tonumber,next,nextvar,print,dofile,dostring 这 7 个是在 table.c 中注册。
readfrom,writeto,appendto,read,write,execute,remove 这 7 个是 IO 库函数,在iolib.c 中注册。
strfind,strlen,strsub,strlower,strupper 这 5 个是字符串库函数,在 strlib.c 中注册。
abs,sin,cos,tan,asin,acos,atan,ceil,floor,mod,sqrt,pow,min,max 这 14 个是数学库函数,在 mathlib.c 中注册。
这一共是 33 (7+7+5+14)个已经注册的函数,注意 33 这个数字。
再看下上面的那个程序打印出来的字节码
CODE
0 SETLINE 2
3 PUSHGLOBAL 3
6 PUSHMARK
7 PUSHGLOBAL 33
10 CALLFUNC
11 SETLINE 2
15 ADJUST 2
16 STOREGLOBAL 34
19 STOREGLOBAL 33
22 SETLINE 3
25 PUSHGLOBAL 33
28 IFFJMP 38
31 SETLINE 4
34 PUSHGLOBAL 4
37 PUSHMARK
38 PUSHGLOBAL 33
41 CALLFUNC
43 ADJUST 0
44 SETLINE 5
47 PUSHGLOBAL 3
50 PUSHMARK
51 PUSHGLOBAL 33
54 CALLFUNC
55 SETLINE 5
59 ADJUST 2
60 STOREGLOBAL 34
63 STOREGLOBAL 33
66 UPJMP 44
69 HALT
简单分析一下这段字节码:
------------------------------
k,v=nextvar(k)
3 PUSHGLOBAL 3
6 PUSHMARK
7 PUSHGLOBAL 33
10 CALLFUNC
PUSHGLOBAL 3 ,这个 3 是什么?注意上面说的注册的 33 个全局变量:
type 为 0,tonumber 为 1,next 为 2, nextvar 为 3,print 为 4... min 为 31, max 为 32。
这些全局变量在符号表中的位置依次递增。
所以 PUSHGLOBAL 3 的意思就是把 nextvar 压栈。
PUSHMARK 标签压栈,这个主要是为了在函数调用时找参数用。
PUSHGLOBAL 33 ,这个 33 是什么?符号表中 33 的位置是 k,就像上面输出的一样。v 为 34。 所以这里的意思是 k 压栈。
CALLFUNC 函数调用,对应的就是代码中的 nextvar(k)。
------------------------------
k,v=nextvar(k)
15 ADJUST 2
16 STOREGLOBAL 34
19 STOREGLOBAL 33
ADJUST 2 调整栈,这个函数有 2 个返回值。
STOREGLOBAL 34
STOREGLOBAL 33
把返回的结果赋值给 k,v。函数返回值的顺序是倒序,所以这里看到的是先给 v 赋值(STOREGLOBAL 34),再给 k 赋值(STOREGLOBAL 33)。
------------------------------
while k do
25 PUSHGLOBAL 33
28 IFFJMP 38
PUSHGLOBAL 33 全局变量 v 压栈。
IFFJMP 38 如果为假(就是 v 为 nil ),向下跳转 38 个字节。
看看跳转到哪儿了,下一条指令的偏移值加上它:31+38 =69; 69 为 HALT,结束。
这里的计算代码执行位置 pc 的方法和在汇编中的效果一样,跳转到的相对位置都是指相对下一条指令的位置。
------------------------------
print(k)
34 PUSHGLOBAL 4
37 PUSHMARK
38 PUSHGLOBAL 33
41 CALLFUNC
43 ADJUST 0
PUSHGLOBAL 4 -- print 压栈
PUSHMARK -- 加标签
PUSHGLOBAL 33 -- k 压栈
CALLFUNC -- 函数调用
ADJUST 0 -- 无返回值
------------------------------
k,v=nextvar(k)
47 PUSHGLOBAL 3
50 PUSHMARK
51 PUSHGLOBAL 33
54 CALLFUNC
55 SETLINE 5
59 ADJUST 2
60 STOREGLOBAL 34
63 STOREGLOBAL 33
66 UPJMP 44
这里的字节码和第一行一样。
最后一句的 UPJMP 为向上跳,下一条指令的偏移 69-44 = 25, 25 行是 PUSHGLOBAL 33 刚好到了 "while k do" 这个地方。