全局变量_G有多特殊?

How special is the global variable _G?

摘自Lua 5.3 manual

_G

A global variable (not a function) that holds the global environment (see §2.2). Lua itself does not use this variable; changing its value does not affect any environment, nor vice versa.

§2.2 中的相关部分

[…] every chunk is compiled in the scope of an external local variable named _ENV, so _ENV itself is never a free name in a chunk.

[…]

Any table used as the value of _ENV is called an environment.

Lua keeps a distinguished environment called the global environment. This value is kept at a special index in the C registry. In Lua, the global variable _G is initialized with this same value. (_G is never used internally.)

When Lua loads a chunk, the default value for its _ENV upvalue is the global environment. Therefore, by default, free names in Lua code refer to entries in the global environment

我知道对于每个加载的块,由于 _ENV 将是第一个上值,它 指向 全局环境 table,指向_G 来自 load.

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

确认两者指向同一个 table。手册声明,而不是多次保证,_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?如果引用计数完成,_G_ENV 释放它时仍然持有引用。像上面的 b 一样,它也应该坚持旧的 table,不是吗?

但是,对于下面的块,_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 的行为变化如此之多,手册最初给出的保证似乎站不住脚。我在这里错过了什么?

全局变量_G有多特别?

它在三个方面很特别:

  1. 它使用了一个名字reserved for internal use by Lua
  2. 它是由 Lua 的标准模块之一创建的(特别是 "base" 模块)。如果你创建一个新的 lua_State 没有 打开 "base" 模块,您将没有 _G 变量。这 独立解释器已经加载了所有标准库, 不过
  3. 一些第三方Lua模块使用了全局变量_G,并且 changing/removing 它可以破坏那些模块。

_G有什么意义?

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

在其他情况下,您想要掌握全局变量 table,例如用于设置元table。 Lua 允许您自定义 tables(和其他值)的行为通过使用 setmetatable 函数,但你必须将 table 作为 参数不知何故。 _G 帮助你做到这一点。

如果您在全局 table 中添加了元 table,在某些情况下 你可能想绕过元方法(__index and/or __newindex) 您刚刚安装。您可以使用 rawgetrawset,但你需要将全局变量 table 作为参数传递 还有。

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

_G_ENV 有什么关系?

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

然而,在任何情况下你都需要一个default环境 在程序员有机会设置一个自定义的之前(或者如果程序员 不想改变它)。在启动时,Lua 创建此默认值 您和商店的环境(也称为 "global environment") 它在 registry。此默认环境用作 _ENV 所有区块的升值,除非您将自定义环境传递给 load or loadfilelua_pushglobaltable还有 直接从注册表中检索这个全局环境,所以 所有 C 模块自动使用它来访问全局变量。

并且如果标准"base"C模块已经加载,这 默认 "global environment" 有一个名为 _G 的 table 字段是指 回到全球环境。

总结一下:

  • 全局变量_G其实是_ENV._G.
  • _ENV不是全局变量,而是上值变量或局部变量。
  • 默认"global environment"的_G字段指向回 全球环境。
  • _G_ENV 默认引用相同的 table(表示全局 环境)。
  • C 代码也不使用,而是注册表中的一个字段( 再次指向全局环境根据定义)。
  • 可以替换_G(在全局环境下)不破坏 C 模块或 Lua 本身(但你可能会破坏第三方 Lua 模块,如果不小心)。
  • 你可以随时替换_ENV,因为它只影响 你自己的代码(目前最多chunk/file)。
  • 如果替换_ENV,可以自己决定是否_G (_ENV._G) 将在受影响的代码中可用,以及它是什么 指向.