来自Lua 5.3手册摘录:
_G
一个全局变量(非函数),保存着全局环境(参见§2.2)。Lua本身不使用此变量;更改其值不会影响任何环境,反之亦然。
§2.2相关部分:
[...] 每个代码块在一个名为
_ENV
的外部局部变量的范围内编译,所以_ENV
本身从不是代码块中的自由名称。[...]
作为
_ENV
值使用的任何表都称为环境。Lua保留了一个特殊的环境,称为全局环境。该值保存在C注册表中的一个特殊索引中。在Lua中,全局变量
_G
被初始化为相同的值。(_G
从不在内部使用。)当Lua加载一个代码块时,其
_ENV
上值的默认值是全局环境。因此,默认情况下,Lua代码中的自由名称指的是全局环境中的条目。
我理解每次加载代码块时,由于_ENV
是第一个上值,所以它被“指向”全局环境表,由load
用_G
进行指向。
> =_G, _ENV
table: 006d1bd8 table: 006d1bd8
确认两者指向同一张表。手册多次明确表示_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
现在同样对_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
?如果进行引用计数,当_ENV
释放它时,_G
仍然持有一个引用。与上面的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
由于_G
的行为有如此多的变化,手册所给出的最初的保证似乎不太可靠。我在这里错过了什么?
_ENV.
,因此在执行脚本前,任何_G
的出现都将变成_ENV._G
。 - Egor Skriptunoff