全局变量_G有多特别?

leg*_*s2k 7 environment lua global-variables lua-table

摘录自Lua 5.3手册:

_G

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

相关部分来自§2.2

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

[...]

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

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

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

我的理解是,每块装,因为_ENV是第一的upvalue,它指出,全球环境表,通过指出_Gload.

> =_G, _ENV
table: 006d1bd8 table: 006d1bd8
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

现在_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
Run Code Online (Sandbox Code Playgroud)

如果_G是一个普通的全球化,为什么它会变成nil这里?如果完成引用计数_G,则在_ENV释放它时仍然保持引用.像b上面一样,它也应该坚持旧桌子,不是吗?

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

_ENV = { _G = _G }
_G.print(_G, _ENV, _ENV._G)   -- old, new, old
Run Code Online (Sandbox Code Playgroud)

但在这里它被杀死了:

_ENV = { g = _G }
_ENV.g.print(_ENV, _ENV.g, _G)    -- new, old, nil
Run Code Online (Sandbox Code Playgroud)

保留它的另一种情况:

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
Run Code Online (Sandbox Code Playgroud)

由于行为有如此多的变化,_G手册给出的原始保证似乎不稳定.我在这里错过了什么?

sif*_*joe 8

全局变量有多特别_G

它有三种特殊之处:

  1. 它使用Lua为内部使用保留的名称.
  2. 它由Lua的标准模块之一(特别是 "基础"模块)创建.如果在lua_State不打开"基础"模块的情况下创建新的,则不会有_G变量.但是,独立解释器已经加载了所有标准库.
  3. 一些第三方Lua模块使用全局变量_G,更改/删除它可能会破坏这些模块.

有什么意义_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_pushglobaltable(它使用注册表而不是_G).因此,用C实现的模块不使用/需要_G 全局变量.这适用于Lua的标准库(在C中实现).事实上,参考手册 的保证,即_G(变量,而不是表)不使用的Lua或它的标准库.

怎么_G涉及_ENV

从版本5.0开始,Lua允许您更改用于在每个(Lua)函数的基础上查找全局变量的表.在Lua 5.0和5.1中你使用了setfenv函数(globals表也称为"函数环境",因此名称setfenv).Lua 5.2使用另一个特殊变量名引入了一种新方法_ENV. _ENV虽然不是一个全局变量,Lua确保每个块都以一个_ENVupvalue开头.新方法的工作原理是让Lua将对非本地(和非upvalue)变量名称的任何访问权转换a_ENV.a.无论_ENV在代码中的那一点,用于解决全局变量.这种方式更安全,因为您无法更改自己未编写的代码环境(不使用调试库),也更灵活,因为您可以通过创建local _ENV有限的变量来更改单个代码块的环境 范围.

但是,在任何情况下,您都需要在程序员有机会设置自定义环境之前使用的默认环境(或者如果程序员不想更改它).在启动时,Lua会为您创建此默认环境(也称为"全局环境")并将其存储在注册表中._ENV除非您将自定义环境传递给load或,否则此默认环境将用作所有块的 upvalue loadfile.lua_pushglobaltable还可以直接从注册表中检索此全局环境,因此所有C模块都会自动使用它来访问全局变量.

如果标准的"基地" C模块已经被加载,这个默认的"全球环境"有一个叫做表字段_G回指向全球环境.

把它们加起来:

  • _G实际上是全局变量_ENV._G.
  • _ENV 不是全局变量,而是upvalue变量或局部变量.
  • _G默认"全局环境" 的字段指向全局环境.
  • _G并且_ENV默认情况下引用相同的表(所述全局环境).
  • C代码既不使用,也不使用注册表中的字段(根据定义,它再次指向全局环境).
  • 您可以替换_G(在全局环境中)而不破坏C模块或Lua本身(但如果不小心,您可能会破坏第三方Lua模块).
  • 您可以随时更换_ENV,因为它只影响您自己的代码(最多当前的块/文件).
  • 如果替换_ENV,您可以自行决定是否_G (_ENV._G)将在受影响的代码中可用,以及它指向的内容.