Lua解析和变量作用域

时间:2022-01-26 06:11:24

近期研究了一下Lua语言在解析时的一些细节,如果在C程序中执行lua脚本的话, 那么变量的作用域是非常值得关注的,这里记录一下在分析过程中得到的一些结论。(本文的描述针对的是lua-5.1.5这个版本的代码)

 

考察下面的两段代码:

scope.lua

 1 b = 700             -- GT['b'] = 700
 2 local a = 9         -- 设置在栈上
 3 
 4 function p1()       -- GT['p1'] = Closure B
 5     m = 90          -- GT['m'] = 90
 6     local n = 8     -- 设置在栈上
 7     print(a)        -- 数据来自upval
 8     print(b)        -- 数据来自GT
 9 end
10 
11 function p2()       -- GT['p2'] = Closure C
12     print(m)        -- 数据来自GT
13     print(n)        -- nil
14 end

srv.c

 1 ...
 2 luaL_loadfile(L, "scope.lua");
 3 lua_pcall(L, 0, 0, 0);      // 执行闭包A的字节码
 4 ...
 5 lua_getglobal(L, "p1");
 6 lua_pcall(L, 0, 0, 0);      // 执行闭包B的字节码
 7 
 8 lua_getglobal(L, "p2");
 9 lua_pcall(L, 0, 0, 0);      // 执行闭包C的字节码
10 ...

 

scope.lua脚本中会生成三个闭包A、B和C,其中闭包A是由scope.lua脚本加载(luaL_loadfile)之后生成的,luaL_loadfile最终会调用f_parse来解析脚本并生成闭包A,并且闭包A的环境table会指定为L的global table。

调用闭包A

接下来在srv.c中第3行将会执行闭包A对应的字节码,操作包括:

  1. 将变量b设置在闭包A的环境Table中
  2. 将变量a设置在栈上
  3. 生成闭包B,赋值给变量p1,同时设置在闭包A的环境Table中,指定闭包B的环境Table就等于闭包A的环境Table。生成的闭包B存在一个upval,指向上一层的局部变量a。
  4. 生成闭包C,赋值给变量p2,同时设置在闭包A的环境Table中,同理闭包C的环境Table也等于闭包A的环境Table。

可以看到变量b、p1和p2都会保存在闭包A的环境Table中,也就是L的global table中。

调用闭包B

在srv.c的第5行执行之后,会将闭包B设置在栈顶,接下来调用lua_pcall便会执行闭包B对应的字节码, 操作如下:

  1. 将变量m设置在闭包B的环境Table中
  2. 将变量n设置在栈上
  3. 从闭包B的upval中找到变量a的值并设置在栈上,调用print
  4. 从闭包B的环境Table中找到变量b的值并设置在栈上,调用print

前面讲过,闭包B的环境Table和闭包A的环境Table是一致的并且都是L的global table,因此可以得到下面的输出结果:

9
700

  

调用闭包C

同样的,srv.c执行到第8行和第9行的时候会执行闭包C的字节码,变量m是从闭包C的环境Table也就是L的global table中获取,而变量n是闭包B的局部变量,没有设置在环境Table中,也不存在于闭包C的upval中,因此结果会为空,得到的结果如下:

90
nil

 


 

总结

现在有很多用C语言实现的服务器程序都会嵌入Lua脚本来提高开发效率,并且通过在一个Lua虚拟机中创建多个Lua线程的手段来对每个请求的处理进行区分,因此在编写Lua脚本的时候要很清楚每个变量的作用域范围,否则可能会出现数据不一致的情况,某些变量可能是被一个Lua虚拟机中的所有Lua线程共享,而某些变量只会存在于一个Lua线程独立的数据栈中。

此外,程序中很有可能还会调用一些API来更改Lua线程的global table或环境table,因此更需要特别关注。


 

参考