全局变量_G有多特殊?

摘自Lua 5.3手册

_G

全局变量(非函数),用于持有全局环境(参阅§2.2)。Lua 本身并不使用此变量;改变它的值不会影响任何环境,反之亦然。

§2.2 中相关的部分

[…] 每个代码块都是在命名为 _ENV 的外部局部变量的作用域中编译的,所以 _ENV 自身在代码块中永远不是自由变量。

[…]

_ENV 的任何表都称为环境。

Lua 保留着一个特殊的全局环境(称作 _G)在 C 注册表中。在 Lua 中,全局变量 _G 和它相同。 (_G 在内部从未使用过.)

当 Lua 加载代码块时,其 _ENV 的默认值为全局环境。因此,默认情况下,Lua 代码中的自由变量引用全局环境中的条目。

我理解每个代码块加载时,由于 _ENV 是第一个 upvalue,它被指向由 load 指向的全局环境表。

> =_G, _ENV
table: 006d1bd8 table: 006d1bd8

确认二者指向同一张表。手册中反复说明,_ENV_G 只是普通的变量名,没有任何隐含含义,Lua 本身也不使用它们。我尝试了下面的代码块:

local a = { }
local b = a      -- 因为 table 是对象,两者都引用同一张表
print(a, b)      -- 相同的地址打印了两次
a = { }          -- 将其之一指向新构造的表
print(a, b)      -- 打印新旧表地址

现在用 _G_ENV 做相同的事情:

local g = _G          -- 做一次额外的引用
print(g, _G, _ENV)    -- 打印三次相同的地址
local p = print       -- 备份 print 以供后用
_ENV = { }            -- 将 `_ENV` 指向一个新表 / 环境
p(g, _G, _ENV)        -- 旧, nil, 新

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)   -- 旧, 新, 旧

但是在这里它被删除了:

_ENV = { g = _G }
_ENV.g.print(_ENV, _ENV.g, _G)    -- 新, 旧, nil

另一种情况下它是被保留的:

print(_G, _ENV)                       -- 相同的地址打印两次
local newgt = {}                      -- 创建新环境
setmetatable(newgt, {__index = _G})   -- 设置 metatable 为 _G 的 __index 元方法
_ENV = newgt                          -- 将 _ENV 指向 newgt
print(_G, newgt, _ENV)                -- 旧, 新, 新

由于 _G 行为的变化如此之多,手册中最初的安慰显得不太可靠。我在这里漏掉了什么?

点赞