全局变量_G有多特别?

时间:2016-03-10 07:24:52

标签: lua global-variables environment lua-table

摘自Lua 5.3 manual

  

_G

     

保存全局环境的全局变量(不是函数)(参见§2.2)。 Lua本身不使用这个变量;改变它的价值不会影响任何环境,反之亦然。

§2.2的相关部分

  

[...]每个块都在名为_ENV的外部局部变量的范围内编译,因此_ENV本身从不是块中的自由名称。

     

[...]

     

任何用作_ENV值的表都称为环境。

     

Lua拥有一个称为全球环境的杰出环境。此值保存在C注册表中的特殊索引处。在Lua中,全局变量_G使用相同的值进行初始化。 (_G从不在内部使用。)

     

当Lua加载一个块时,其_ENV upvalue的默认值是全局环境。因此,默认情况下,Lua代码中的自由名称是指全局环境中的条目

我理解,对于每个加载的块,由于_ENV将是第一个upvalue,因此指向全局环境表,由_G指向{{1 }}

load

确认两者都指向同一张表。手册陈述,而不是多次保证,> =_G, _ENV table: 006d1bd8 table: 006d1bd8 _ENV只是常规名称,没有隐藏的意义,Lua本身不在内部使用它。我在下面尝试了这个块:

_G

现在对local a = { } local b = a -- since tables are objects, both refer to the same table object print(a, b) -- same address printed twice a = { } -- point one of them to a newly constructed table print(a, b) -- new, old table addresses printed _G执行相同操作:

_ENV

如果local g = _G -- make an additional reference print(g, _G, _ENV) -- prints same address thrice local p = print -- backup print for later use _ENV = { } -- point _ENV to a new table/environment p(g, _G, _ENV) -- old, nil, new table: 00ce1be0 table: 00ce1be0 table: 00ce1be0 table: 00ce1be0 nil table: 00ce96e0 是一个普通的全球,为什么它会变成_G?如果引用计数已完成,nil仍然在_G释放时引用了引用。就像上面的_ENV一样,它也应该坚持旧表,不是吗?

但是,对于下面的块,b保持不变/保留!

_G

但在这里被杀死了:

_ENV = { _G = _G }
_G.print(_G, _ENV, _ENV._G)   -- old, new, old

保留它的另一种情况:

_ENV = { g = _G }
_ENV.g.print(_ENV, _ENV.g, _G)    -- new, old, nil

由于print(_G, _ENV) -- print same address twice local newgt = {} -- create new environment setmetatable(newgt, {__index = _G}) -- set metatable with _G as __index metamethod _ENV = newgt -- point _ENV to newgt print(_G, newgt, _ENV) -- old, new, new 的行为有很多变化,手册给出的原始保证似乎不稳定。我在这里缺少什么?

2 个答案:

答案 0 :(得分:8)

全局变量_G有多特别?

它有三种特殊之处:

  1. 它使用名称reserved for internal use by Lua
  2. 它是由Lua的标准模块之一创建的(尤其是 "base"模块)。如果您没有创建新的lua_State 打开"基地"模块,你没有_G变量。该 独立解释器已经加载了所有标准库, 虽然。
  3. 某些第三方Lua模块使用全局变量_G,和 更改/删除它可能会破坏这些模块。
  4. _G

    的重点是什么?

    Lua中的全局变量使用普通表实现。任何 访问不是local变量或upvalue的变量 被重定向到这个表。局部变量总是优先考虑的 如果你有一个全局变量和一个同名的局部变量, 你将永远得到当地的。在这里_G发挥作用:如果 你想要全局变量,你可以说_G.name而不是name。 假设名称_G不是局部变量(它是为Lua保留的, 还记得吗?!),这总能得到全局变量的值 通过使用表索引语法,从而消除歧义 局部变量名。在较新的Lua版本(5.2+)中,您也可以使用 _ENV.name作为替代方案,但_G早于这些版本 保持兼容性。

    在其他情况下,您希望获得全局变量 表格,例如用于设置元表。 Lua允许您自定义 通过使用设置元表来表(和其他值)的行为 setmetatable函数,但你必须将表作为一个 参数不知何故。 _G可以帮助您实现这一目标。

    如果您在globals表中添加了metatable,在某些情况下 你可能想绕开元方法(__index和/或 __newindex)您刚安装完毕。您可以使用rawgetrawset,但您需要将globals表作为参数传递 同样。

    请注意,上面列出的所有用例仅适用于Lua代码而不是 C代码。在C代码中,您没有命名局部变量,只有堆栈 指数。所以没有歧义。如果你想要参考 globals表传递给某些函数,可以使用 lua_pushglobaltableuses the registry代替_G)。 因此,用C实现的模块不需要_G 全局变量。这适用于Lua的标准库(即 也在C)中实施。实际上,参考手册 guarantees,未使用_G(变量,而不是表) Lua或其标准库。

    _G_ENV的关系如何?

    从5.0版开始,Lua允许您更改用于查找的表 基于per(Lua)函数的全局变量。在Lua 5.0和5.1中 你使用了setfenv函数(全局表是 也称为"功能环境",因此名称为setfenv)。 Lua 5.2 使用另一个特殊变量名_ENV引入了一种新方法。 _ENV 不是一个全局变量,Lua确保每一个 chunk以_ENV upvalue开头。新方法通过让出来 Lua转换对非本地(和非upvalue)变量的任何访问 将a命名为_ENV.a。无论代码中的_ENV是什么 习惯于解决全局变量。这种方式更安全,因为 你无法改变自己没有写过的代码环境 (不使用调试库),也因为你更灵活 可以通过创建来更改单个代码块的环境 范围有限的local _ENV个变量。

    但是,无论如何,您需要使用默认环境 在程序员有机会设置自定义程序之前(或者程序员 不想改变它)。在启动时,Lua创建此默认值 适用于您和商店的环境(也称为"global environment") 它在registry。此默认环境用作 除非您将自定义环境传递给所有块,否则_ENV值为upvalue loadloadfilelua_pushglobaltable也是 直接从注册表中检索此全局环境 所有C模块都自动使用它来访问全局变量。

    如果标准" base"这个C模块已经加载了 默认"全球环境"有一个名为_G的表字段引用 回到全球环境。

    总结一下:

    • 全局变量_G实际上是_ENV._G
    • _ENV不是全局变量,而是upvalue变量或局部变量。
    • 默认"全球环境的_G字段"回到 全球环境。
    • 默认情况下,
    • _G_ENV引用同一个表格(表示全局 环境)。
    • C代码既没有使用,也没有使用注册表中的字段(哪个 再次指向全球环境按定义)。
    • 您可以在不中断的情况下替换_G(在全局环境中) C模块或Lua本身(但你可能会打破第三方Lua 模块,如果不小心)。
    • 您可以随时替换_ENV,因为它只会影响 你自己的代码(最多当前的块/文件)。
    • 如果您替换_ENV,您可以自行决定是否_G_ENV._G)将在受影响的代码中提供,以及它是什么 指向。

答案 1 :(得分:-1)

Lua5.3中的_G或_ENV特殊示例

通过阅读参考资料和用户注释以及答案,我想知道为什么默认情况下无法对如此重要的表进行元映射。就像Lua5.3中的字符串一样。他们已经使用包含几乎所有字符串函数的表对__index进行了元表化。因此,我决定检查发生了什么,以及元可配置的_G(或_ENV)有多有用。我能说/写什么。我发现这对我非常有用,而且副作用是,所有已定义的__index函数/变量/对象都是全局的。一个简单的示例是定义一个printf()函数,因此可以在lua -i中自己尝试一下...

# lua -i
Lua 5.3.5  Copyright (C) 1994-2018 Lua.org, PUC-Rio
> setmetatable(_G,{__index={}})
table: 0x566816d0
> -- Using getmetatable(_G).__index.new=whatever now
> -- for setting/changing after setmetatable()
> getmetatable(_G).__index.printf=function(s,...) io.write(string.format(s,...)) end
> printf
function: 0x5669bee0
> printf("%s\n",_VERSION)
Lua 5.3

> printf("%s\n",_VERSION:rep(10,' is cool '))
Lua 5.3 is cool Lua 5.3 is cool Lua 5.3 is cool Lua 5.3 is cool Lua 5.3 is cool Lua 5.3 is cool Lua 5.3 is cool Lua 5.3 is cool Lua 5.3 is cool Lua 5.3

> printf("%s\n",_VERSION.dump(printf))
uaS�
�
xV(w@=stdiF@�@@��@�-�d@&�printstringforma_ENV

所以-在工作之后,我将所有表函数放在__index中,并将dump()函数显示表内容。 help()函数还有一个位置,用于显示例如元表的内容。

_G:help()
__index=table: 0x5669bbe0
printf=function: 0x5669eee0
sort=function: 0x56665b60
help=function: 0x566a0de0
concat=function: 0x566655b0
remove=function: 0x56665300
pack=function: 0x56664dc0
dump=function: 0x5669bc40
move=function: 0x56665060
unpack=function: 0x56664c80
insert=function: 0x56665450
> -- Now _Global __index.help call on string _VERSION
help(_VERSION)
__index=table: 0x566846b0
find=function: 0x56664550
gmatch=function: 0x56661e70
rep=function: 0x566619f0
char=function: 0x56661f10
byte=function: 0x566614b0
dump=function: 0x56662c80
match=function: 0x56664540
upper=function: 0x566617a0
unpack=function: 0x56663250
format=function: 0x56662030
len=function: 0x566612c0
gsub=function: 0x56664560
packsize=function: 0x56663110
pack=function: 0x566635f0
sub=function: 0x566618c0
lower=function: 0x56661ba0
reverse=function: 0x56661830

第一次调用:会将_G放入help()(help(_G))-第二个调用显示已定义字符串的元表。