Насколько особенная глобальная переменная _G?

Выдержка из руководства по Lua 5.3:

_G

Глобальная переменная (не функция), которая содержит глобальную среду (см. §2.2). Сам Lua не использует эту переменную; изменение его значения не влияет ни на какую среду, и наоборот.

Соответствующая часть из §2.2

[…] Каждый чанк компилируется в области видимости внешней локальной переменной с именем _ENV, так что _ENV сам по себе никогда не является свободным именем в чанке.

[…]

Любая таблица, используемая в качестве значения _ENV, называется средой.

Lua поддерживает особую среду, называемую глобальной средой. Это значение хранится в специальном индексе в реестре C. В Lua глобальная переменная _G инициализируется этим же значением. (_G никогда не используется внутри компании.)

Когда Lua загружает чанк, значением по умолчанию для его _ENV upvalue является глобальная среда. Поэтому по умолчанию свободные имена в коде Lua относятся к записям в глобальной среде.

Я понимаю, что для каждого загруженного фрагмента, поскольку _ENV будет первым повышающим значением, он указывает на глобальную таблицу среды, на которую указывает _G load.

> =_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 здесь? Если подсчет ссылок выполнен, _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

При таком большом количестве вариаций в поведении _G первоначальное заверение, данное в руководстве, кажется шатким. Что мне здесь не хватает?


person legends2k    schedule 10.03.2016    source источник
comment
Все глобальные переменные автоматически добавляются к _ENV.. Таким образом, любое вхождение _G становится _ENV._G перед выполнением вашего скрипта.   -  person Egor Skriptunoff    schedule 10.03.2016
comment
Отличный момент! Несмотря на то, что я знал об этом факте, я не смог применить его в данной ситуации. Не могли бы вы превратить этот комментарий в ответ? Это полностью разгадывает загадку.   -  person legends2k    schedule 10.03.2016


Ответы (2)


Насколько особенная глобальная переменная _G?

Он особенный по трем параметрам:

  1. Он использует имя , зарезервированное для внутреннего использования Lua.
  2. Он создается одним из стандартных модулей Lua (в частности, "base" модуль). Если вы создадите новый lua_State, не открывая «базовый» модуль, у вас не будет переменной _G. Однако в автономном интерпретаторе уже загружены все стандартные библиотеки.
  3. Некоторые сторонние модули Lua используют глобальную переменную _G, и ее изменение / удаление может привести к поломке этих модулей.

Какой смысл в _G?

Глобальные переменные в Lua реализованы с использованием обычной таблицы. Любой доступ к переменной, которая не является local переменной или повышающим значением, будет перенаправлен в эту таблицу. Локальные переменные всегда имеют приоритет, поэтому, если у вас есть глобальная переменная и локальная переменная с тем же именем, вы всегда получите локальную переменную. И здесь в игру вступает _G: если вам нужна глобальная переменная, вы можете сказать _G.name вместо name. Предполагая, что имя _G не является локальной переменной (оно зарезервировано для Lua, помните ?!), это всегда будет давать вам значение глобальной переменной, используя синтаксис индексации таблицы и, таким образом, устраняя двусмысленность с именами локальных переменных. В более новых версиях Lua (5.2+) вы также можете использовать _ENV.name в качестве альтернативы, но _G предшествует этим версиям и сохраняется для совместимости.

Есть и другие случаи, когда вы хотите получить доступ к таблице глобальных переменных, например для установки метатаблицы. Lua позволяет настраивать поведение таблиц (и других значений), устанавливая метатаблицу с помощью _ 13_, но вам нужно каким-то образом передать таблицу в качестве параметра. _G поможет вам в этом.

Если вы добавили метатаблицу в таблицу глобальных переменных, в некоторых случаях вам может потребоваться обойти только что установленные метаметоды (__index и / или __newindex). Вы можете использовать rawget и _ 18_, но вам также необходимо передать таблицу глобальных переменных в качестве параметра.

Обратите внимание, что все перечисленные выше варианты использования относятся только к коду Lua не на C. В коде C у вас нет именованных локальных переменных, только индексы стека. Так что двусмысленности нет. И если вы хотите, чтобы ссылка на таблицу глобальных переменных передавалась какой-либо функции, вы можете использовать lua_pushglobaltable (который использует реестр вместо _G ). Как следствие, модули, реализованные на C, не используют / не нуждаются в глобальной переменной _G. Это относится и к стандартной библиотеке Lua (которая реализована на C). Фактически, справочное руководство гарантирует, что _G (переменная , а не таблица) не используется Lua или его стандартной библиотекой.

Как _G относится к _ENV?

Начиная с версии 5.0 Lua позволяет вам изменять таблицу, используемую для поиска глобальных переменных, для каждой функции (Lua-). В Lua 5.0 и 5.1 вы использовали для этого функцию setfenv ( таблица глобальных переменных также называется «средой функций», отсюда и название setfenv). Lua 5.2 представил новый подход с использованием другого специального имени переменной _ENV. _ENV не глобальная переменная, Lua гарантирует, что каждый блок начинается с _ENV upvalue. Новый подход работает, позволяя Lua переводить любой доступ к нелокальной (и не имеющей значения) переменной с именем a в _ENV.a. Независимо от того, что _ENV находится в этой точке кода, используется для разрешения глобальных переменных. Этот способ намного безопаснее, потому что вы не можете изменить среду кода, который вы не писали сами (без использования библиотеки отладки), а также более гибкий, потому что вы можете изменить среду для отдельных блоков кода, создав переменные local _ENV с ограниченными возможностями.

Однако в любом случае вам понадобится среда по умолчанию, которая используется до того, как программист сможет установить пользовательскую среду (или если программист не хочет ее менять). При запуске Lua создает эту среду по умолчанию (также называемую "глобальная среда" ) для вас и сохраняет его в реестре. Эта среда по умолчанию используется как _ENV upvalue для всех фрагментов, если вы не передаете настраиваемые среды в load или loadfile. lua_pushglobaltable также извлекает эту глобальную среду непосредственно из реестра, поэтому все модули C автоматически используют ее для доступа к глобальным переменным.

И если загружен стандартный «базовый» модуль C, эта «глобальная среда» по умолчанию имеет поле таблицы с именем _G, которое ссылается на глобальную среду.

Подвести итог:

  • Глобальная переменная _G на самом деле _ENV._G.
  • _ENV - это не глобальная, а повышающая или локальная переменная.
  • Поле _G "глобальной среды" по умолчанию указывает на глобальную среду.
  • _G и _ENV по умолчанию относятся к одной и той же таблице (указанной глобальной среде).
  • Код C не использует ни то, ни другое, но поле в реестре (которое снова указывает на глобальную среду по определению).
  • Вы можете заменить _G (в глобальной среде), не нарушая модули C или сам Lua (но вы можете сломать сторонние модули Lua, если не будете осторожны).
  • Вы можете заменить _ENV, когда захотите, потому что это влияет только на ваш собственный код (максимум на текущий фрагмент / файл).
  • Если вы замените _ENV, вы можете решить для себя, будет ли _G (_ENV._G) доступен в затронутом коде и на что он указывает.
person siffiejoe    schedule 14.03.2016

Специальный пример _G или _ENV в Lua5.3

Чтение ссылок, комментариев и ответов пользователей позволяет мне задаться вопросом, почему такая важная таблица не имеет метатаблицы по умолчанию. Как и строки в Lua5.3. У них есть метатаблица __index с таблицей, содержащей почти все строковые функции. Поэтому я решил проверить, что происходит и насколько полезной может быть метатаблица _G (или _ENV). А что я могу сказать / написать. Я обнаружил, что это очень полезно, и в качестве побочного эффекта все определенные функции / переменные / объекты __index являются глобальными. Простым примером является определение функции printf(), поэтому попробуйте ее самостоятельно в _5 _...

# 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)) - второй показывает метатаблицу определенной строки.

person koyaanisqatsi    schedule 11.08.2020