声明变量和Lua作用域问题

我是Bitfighter的领头开发者,我们使用Lua作为脚本语言,允许玩家编写自己定制的机器人飞船。

在Lua中,你不需要声明变量,除非另有说明,所有变量都默认为全局作用域。这会导致一些问题。例如以下代码片段:

loc = bot:getLoc()
items = bot:findItems(ShipType)     -- Find a Ship

minDist = 999999
found = false

for indx, item in ipairs(items) do
   local d = loc:distSquared(item:getLoc())

   if(d < minDist) then
      closestItem = item
      minDist = d
   end
end

if(closestItem != nil) then
   firingAngle = getFiringSolution(closestItem)
end

在此代码片段中,如果findItems()没有返回任何候选项,closestItem仍将引用上次发现的任何飞船,而在此期间,该飞船可能已被击败。如果船被摧毁,它将不再存在,getFiringSolution()将失败。

你发现了问题吗?那么我的用户也不会注意到。这是微妙的,但却有巨大的影响。

一个解决办法是要求所有变量都声明,并且所有变量默认为局部作用域。虽然这种变化不会使编程人员无法引用不再存在的对象,但会使无意间这样做变得更加困难。

有没有办法告诉Lua将所有变量默认为局部作用域,并/或要求声明它们?我知道一些其他语言(如Perl)可以使用此选项。

谢谢你!


这里有很多好的答案,谢谢!

我决定采用稍微修改过的Lua 'strict'模块。这似乎可以让我达到想要的效果,并且我将稍加修改以改进消息并使其更适合我的特定上下文。

原文链接 https://stackoverflow.com/questions/1014757

点赞
stackoverflow用户121674
stackoverflow用户121674

在 Lua 中,全局变量名义上存在于全局表 _G 中(实际上更为复杂,但从 Lua 方面来说,我认为没有办法区分)。和其他 Lua 表一样,可以将 __newindex 元表附加到 _G 上,以控制变量被添加到其中的方式。让这个 __newindex 处理程序在创建全局变量时执行您想要执行的任何操作:抛出错误,允许但打印警告等等。

为了干涉 _G,最简单、最清晰的方法是使用 setfenv。请参阅文档

2009-06-18 20:16:38
stackoverflow用户88888888
stackoverflow用户88888888

没有选项可以设置这种行为,但是标准安装提供了一个名为“strict”的模块,可以通过修改元表来实现此功能。

使用方法:require 'strict'

有关更详细的信息和其他解决方案,请参阅http://lua-users.org/wiki/DetectingUndefinedVariables,但我推荐使用“strict”。

2009-06-18 20:56:29
stackoverflow用户68204
stackoverflow用户68204

实际上,具有过时引用到飞船的额外全局变量足以防止 GC 丢弃对象。因此,可以通过注意到该飞船现在已经“死亡”并拒绝对其执行任何操作来在运行时检测到它。它仍然不是正确的飞船,但至少不会崩溃。

你可以做的一件事是保持用户脚本处于 沙盒 中,可能是每个脚本一个沙盒。通过正确处理沙盒的环境表或元表,您可以在调用用户代码之前或之后排列在沙盒中丢弃所有或大多数全局变量。

在调用后清理沙盒的好处是丢弃不应该挂起的额外引用。这可以通过保留白名单中允许在环境中保留的字段并删除所有其他字段来完成。

例如,以下实现了使用仅包含白名单名称的环境与为每个调用提供的新刮擦表的用户提供的函数的沙盒调用。

-- table of globals that will available to user scripts
local user_G = {
        print=_G.print,
        math=_G.math,
        -- ...
    }
-- metatable for user sandbox
local env_mt = { __index=user_G }

-- call the function in a sandbox with an environment in which new global
-- variables can be created and modified but they will be discarded when the
-- user code completes.
function doUserCode(user_code, ...)
    local env = setmetatable({}, env_mt) -- create a fresh user environment with RO globals
    setfenv(user_code, env)        -- hang it on the user code
    local results = {pcall(user_code, ...)}
    setfenv(user_code,{})
    return unpack(results)
end

如果您希望,可以通过将全局表推回到一个元表访问之后再将其设为只读来扩展此功能。

请注意,完整的沙盒解决方案还将考虑对无意或有意执行无限(或仅仅很长)循环或其他操作的用户代码的处理方法。这方面的一般解决方案是 Lua 列表 上的讨论主题,但好的解决方案比较难。

2009-06-19 01:21:34
stackoverflow用户6236
stackoverflow用户6236

默认情况下局部变量是错误的。请参见:

http://lua-users.org/wiki/LocalByDefault

http://lua-users.org/wiki/LuaScopingDiscussion

你需要使用某种全局环境保护。有一些静态工具可以做到这一点(不是很成熟),但最常见的解决方案是使用基于 _G 的元表中的 __index__newindex 的运行时保护。

不要忘了这个页面可能也很有用:

http://code.google.com/p/lua-alchemy/wiki/LuaGlobalEnvironmentProtection

请注意,虽然它讨论的是嵌入到swf中的Lua,但所描述的技术(参见源代码)也适用于通用的Lua。我们在工作中的生产代码中采用了类似的技术。

2009-06-19 07:39:14