Ответ 1
Я действительно хочу, чтобы вы назвали эти переменные немного более разумно. Поэтому я буду:
local inner = 'asdf'
local function b()
return inner
end
inner = 10
return b
и
func = require 'test'
func()
ОК, теперь, когда мы знаем, о чем говорим, я могу продолжить.
Lua chunk test
имеет локальную переменную с именем inner
. Внутри этого фрагмента вы создаете новую функцию b
. Так как это новая функция, она имеет область действия в пределах фрагмента test
.
Поскольку он находится внутри функции, он имеет право доступа к локальным переменным, объявленным вне этой функции. Но поскольку он находится внутри функции, он не получает доступа к таким переменным, как к одному из своих локальных. Компилятор обнаруживает, что inner
- это локальная переменная, объявленная за пределами области действия, поэтому она преобразует ее в то, что Lua называет "upvalue".
Функции в Lua могут иметь произвольное количество значений (до 255), связанных с ними, называемое "upvalues". Функции, созданные на C/С++, могут хранить некоторое количество upvalues, используя lua_pushcclosure
. Функции, созданные компилятором Lua, используют upvalues для обеспечения лексического охвата.
Область - это все, что происходит в фиксированном блоке кода Lua. Итак:
if(...) then
--yes
else
--no
end
Блок yes
имеет область видимости, а блок no
имеет разную область. Любые переменные local
, объявленные в блоке yes
, не могут быть доступны из блока no
, потому что они находятся за пределами области no
.
Конструкции Lua, которые определяют область действия, это if/then/else/end
, while/do/end
, repeat/until
, do/end
, for/end
и function/end
. Кроме того, каждый script, называемый "куском" Lua, имеет область действия.
Вложенные объекты. В пределах одной области действия вы можете получить доступ к локальным переменным, объявленным в более высокой области.
"Стек" представляет все переменные, объявленные как local
в пределах определенной области. Поэтому, если у вас нет локальных переменных в определенной области, стек для этой области пуст.
В C и С++ "стек", с которым вы знакомы, является всего лишь указателем. Когда вы вызываете функцию, компилятор предопределил, сколько байтов пространства требуется для стека функций. Он продвигает указатель на эту сумму. Все переменные стека, используемые в функции, являются просто байтовыми смещениями из указателя стека. Когда функция завершается, указатель стека уменьшается на величину стека.
В Lua все по-другому. Стек для конкретной области видимости - это объект, а не только указатель. Для любой конкретной области действия существует определенное количество переменных local
. Когда интерпретатор Lua входит в область действия, он "распределяет" стек размера, необходимого для доступа к этим локальным переменным. Все ссылки на локальные переменные являются просто смещениями в этот стек. Доступ к локальным переменным из более высоких областей (ранее определенных) просто обеспечивает доступ к другому стеку.
Итак, в Lua у вас концептуально есть стек стеков (который я буду называть "s-стеком" для ясности). Каждая область создает новый стек и толкает его, и когда вы покидаете область действия, она выталкивает стек из s-стека.
Когда компилятор Lua встречает ссылку на переменную local
, она преобразует эту ссылку в индекс в s-стек и смещение в этот конкретный стек. Поэтому, если он обращается к переменной в текущем локальном стеке, индекс в s-стек относится к вершине s-стека, а смещение - это смещение в этот стек, где находится переменная.
Это прекрасно для большинства конструкций Lua, которые имеют доступ к областям. Но function/end
не просто создают новую область; они создают новую функцию. И этой функции разрешен доступ к стекам, которые не только локальный стек этой функции.
Стеки - это объекты. И в Lua объекты подлежат сборке мусора. Когда интерпретатор входит в область действия, он выделяет объект стека и толкает его. Пока объект стека выталкивается в s-стек, он не может быть уничтожен. Стек стеков относится к объекту. Однако, как только интерпретатор выходит из области действия, он выталкивает стек из s-стека. Так как он больше не ссылается, он подлежит сбору.
Однако функция, которая обращается к переменным за пределами собственной локальной области, все равно может ссылаться на этот стек. Когда компилятор Lua видит ссылку на переменную local
, которая не входит в локальную область функции, она изменяет эту функцию. Он определяет, к какому стек относится местное, на которое он ссылается, и затем сохраняет этот стек как верхнее значение в функции. Он преобразует ссылку на эту переменную в смещение на это значение upvalue, а не на смещение в стек, который в настоящее время находится в s-стеке.
Итак, пока объект функции продолжает существовать, так же, как и стек (ы), который он ссылается.
Помните, что стеки динамически создаются и уничтожаются, когда интерпретатор Lua входит и выходит из области функций. Поэтому, если вы дважды запускаете test
, вызывая loadfile
и дважды выполняя возвращенную функцию, вы получите две отдельные функции, которые относятся к двум отдельным стекам. Ни одна из функций не увидит значение другого.
Обратите внимание, что это может быть не так, как это реализовано, но что общая идея позади.