Реализация замыканий в Lua? - PullRequest
11 голосов
/ 16 октября 2011

У меня есть вопрос о том, как реализованы замыкания.

Скажем, это в файле с именем test.lua:

local a = 'asdf'

local function b()
    return a
end

a = 10

return b

А другой файл делает

a = require 'test'
a()

будет напечатано

10

Если a - это указатель в стеке на 'asdf' (в куче, я полагаю, но это не имеет значения), а замыкание b равнопредположительно, созданный таким образом, адрес, который был в a, сохранен для использования b, как a = 10 также меняет указатель внутри замыкания?

Википедия достаточно хорошо говорит, что меня смущает:

Реализация языка не может легко поддерживать полное замыкание, если его модель памяти времени выполнения выделяет все локальные переменные в линейном стеке 1 .В таких языках локальные переменные функции освобождаются, когда функция возвращается.

Я думал, что, возможно, b действительно не сохранял указатель на 'asdf', а смещение стека a, так что вы можете изменить a и смещение стека даст вам a, которое указывает на последнюю вещь, которую вы установили a, но тогда как это работает, когда a (указатель) выталкиваетсявне стека, и смещение стека становится недействительным?

1 Я знаю, что Lua не выделяет значения в стеке, но выделяет локальные указатели в стекек значениям в куче, не так ли?

1 Ответ

22 голосов
/ 16 октября 2011

Мне бы очень хотелось, чтобы вы назвали эти переменные более разумно. Итак, я буду:

local inner = 'asdf'

local function b()
    return inner
end

inner = 10

return b

и

func = require 'test'
func()

Хорошо, теперь, когда мы знаем, о чем говорим, я могу продолжить.

Блок Lua test имеет локальную переменную с именем inner. В этом блоке вы создаете новую функцию b. Так как это новая функция, она имеет область видимости в области чанка test.

Поскольку он находится внутри функции, он имеет право доступа к локальным переменным, объявленным вне этой функции. Но поскольку он находится внутри функции, он не имеет доступа к этим переменным, как если бы он был одним из своих локальных элементов. Компилятор обнаруживает, что inner является локальной переменной, объявленной вне области действия функции, поэтому он преобразует ее в то, что Lua называет «повышающим значением».

Функции в Lua могут иметь произвольное количество значений (до 255), связанных с ними, называемых «повышенными значениями». Функции, созданные в C / C ++, могут хранить некоторое количество повышений с помощью lua_pushcclosure. Функции, созданные компилятором Lua, используют значения up для обеспечения лексической области видимости.

Область действия - это все, что происходит в фиксированном блоке кода 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. Кроме того, каждый скрипт, называемый Lua «чанк», имеет область действия.

Области являются вложенными. Из одной области вы можете получить доступ к локальным переменным, объявленным в более высокой области.

«Стек» представляет все переменные, объявленные как local в определенной области видимости. Поэтому, если у вас нет локальных переменных в определенной области, стек для этой области пуст.

В C и C ++ знакомый вам «стек» - это просто указатель. Когда вы вызываете функцию, компилятор заранее определяет, сколько байт пространства требуется стеку функции. Он увеличивает указатель на эту сумму. Все переменные стека, используемые в функции, являются просто байтовыми смещениями от указателя стека. При выходе из функции указатель стека уменьшается на величину стека.

В Луа все по-другому. Стек для конкретной области видимости - это объект , а не просто указатель. Для любой конкретной области существует некоторое количество local переменных, определенных для нее. Когда интерпретатор Lua входит в область действия, он «выделяет» стек размера, необходимого для доступа к этим локальным переменным. Все ссылки на локальные переменные просто смещаются в этот стек. Доступ к локальным переменным из более высоких областей (ранее определенных) просто обращается к другому объекту стека.

Так что в Lua у вас концептуально есть стек стеков (для ясности я буду называть его "s-stack"). Каждая область создает новый стек и выталкивает его, а когда вы покидаете область, он выталкивает стек из s-стека.

Когда компилятор Lua обнаруживает ссылку на переменную local, он преобразует эту ссылку в индекс в s-стек, а смещение в этот конкретный стек. Таким образом, если он обращается к переменной в текущем локальном стеке, индекс в s-стеке относится к вершине s-стека, а смещение - это смещение в тот стек, где находится переменная.

Это нормально для большинства конструкций Lua, которые имеют доступ к областям действия. Но function/end не просто создает новую область видимости; они создают новую функцию. И этой функции разрешен доступ к стекам, которые не являются только локальным стеком этой функции.

Стеки являются объектами.А в Lua объекты подлежат сборке мусора.Когда интерпретатор входит в область действия, он выделяет объект стека и выталкивает его.Пока объект стека помещается в s-стек, он не может быть уничтожен.Стек стеков относится к объекту.Однако, как только интерпретатор выходит из области видимости, он выталкивает стек из s-стека.Таким образом, поскольку на него больше нет ссылок, он подлежит сбору.

Однако функция, которая обращается к переменным вне своей локальной области видимости, все еще может ссылаться на этот стек.Когда компилятор Lua видит ссылку на переменную local, которая не входит в локальную область функции, он изменяет функцию.Он выясняет, к какому стеку принадлежит локальная ссылка, на которую он ссылается, а затем сохраняет этот стек в качестве значения в функции.Он преобразует ссылку на эту переменную в смещение в это конкретное значение, а не смещение в стек, который в данный момент находится в s-стеке.

Так что, пока объект функции продолжает существовать, такжебудут стек (ы), на которые он ссылается.

Помните, что стеки динамически создаются и уничтожаются, когда интерпретатор Lua входит и выходит из области функций.Таким образом, если бы вы запускали test дважды, вызывая loadfile и выполняя возвращаемую функцию дважды, вы бы получили две отдельные функции, которые ссылаются на два отдельных стека .Ни одна из функций не увидит значение от другого.

Обратите внимание, что это может быть не совсем так, как это реализовано, но это общая идея, лежащая в основе.

...