在Lua中实现关闭?

Set*_*gie 11 lua implementation closures

我有一个关于如何实现闭包的问题.

假设这是在一个名为的文件中test.lua:

local a = 'asdf'

local function b()
    return a
end

a = 10

return b
Run Code Online (Sandbox Code Playgroud)

而另一个文件呢

a = require 'test'
a()
Run Code Online (Sandbox Code Playgroud)

它会打印出来

10
Run Code Online (Sandbox Code Playgroud)

如果a是堆栈上的指针'asdf'(在堆上我假设,但没关系),并且b创建了闭包,因此大概a是保存的地址供b使用,如何a = 10将闭包内的指针更改为好?

维基百科很好地说了令我困惑的事情:

如果语言实现的运行时内存模型在线性堆栈1上分配所有局部变量,则它无法轻松支持完全闭包.在这些语言中,函数返回时会释放函数的局部变量.

我在想,或许b真的没有保存指针'asdf'但是堆栈偏移量a,这样你就可以改变a,堆栈偏移量会让你到达a你设置的最后一个点a,但是那时它是如何工作的a(从堆栈中弹出指针)堆栈偏移变为无效?

1我知道Lua不会在堆栈上分配,但它会将堆栈上的本地指针分配给堆中的值,不是吗?

Nic*_*las 22

我真的希望你能更合理地命名这些变量.所以我会:

local inner = 'asdf'

local function b()
    return inner
end

inner = 10

return b
Run Code Online (Sandbox Code Playgroud)

func = require 'test'
func()
Run Code Online (Sandbox Code Playgroud)

好的,既然我们知道我们在谈论什么,我就可以继续了.

Lua块test有一个名为的局部变量inner.在该块中,您可以创建一个新功能b.由于这是一个新函数,因此它的范围在块的范围内test.

由于它在函数内,因此它有权访问在该函数之外声明的局部变量.但是因为它在一个函数内部,所以它不会像它自己的本地一样访问那些变量.编译器检测到这inner是在函数范围之外声明的局部变量,因此它将其转换为Lua所称的"upvalue".

Lua中的函数可以具有与它们相关联的任意数量的值(最多255个),称为"upvalues".在C/C++中创建的函数可以使用存储一定数量的upvalues lua_pushcclosure.Lua编译器创建的函数使用upvalues来提供词法作用域.

范围是在固定的Lua代码块中发生的所有事情.所以:

if(...) then
  --yes
else
  --no
end
Run Code Online (Sandbox Code Playgroud)

所述yes块具有一个范围,该no块具有不同的范围.无法从块中访问块中local声明的任何变量,因为它们不在块的范围内.yesnono

Lua中构造了定义范围是if/then/else/end,while/do/end,repeat/until,do/end,for/end,和function/end.此外,每个称为Lua"chunk"的脚本都有一个范围.

范围是嵌套的.在一个范围内,您可以访问在更高范围内声明的局部变量.

"堆栈"表示local在特定范围内声明的所有变量.因此,如果某个范围内没有局部变量,则该范围的堆栈为空.

在C和C++中,您熟悉的"堆栈"只是一个指针.当您调用函数时,编译器已预先确定函数堆栈需要多少字节的空间.它将指针推进该量.函数中使用的所有堆栈变量都只是堆栈指针的字节偏移量.当函数退出时,堆栈指针减少堆栈量.

在Lua,情况有所不同.特定范围的堆栈是一个对象,而不仅仅是一个指针.对于任何特定范围,都local为其定义了一些变量.当Lua解释器进入作用域时,它"分配"一个访问这些局部变量所需大小的堆栈.对局部变量的所有引用都只是对该堆栈的偏移.从较高范围(先前定义的)访问本地变量只是访问不同的堆栈对象.

所以在Lua中,你在概念上有一堆堆栈(为了清楚起见,我将其称为"s-stack").每个作用域都会创建一个新的堆栈并将其推送,当您离开作用域时,它会从s-stack中弹出堆栈.

当Lua编译器遇到对local变量的引用时,它会将该引用转换为s-stack中的索引,以及该特定堆栈的偏移量.因此,如果它访问当前本地堆栈中的变量,则s-stack中的索引引用s-stack的顶部,而offset是该变量所在的堆栈的偏移量.

这对于访问范围的大多数Lua构造来说都很好.但是function/end,不要只是创造一个新的范围; 他们创造了一个新的功能.并且允许此函数访问不仅是该函数的本地堆栈的堆栈.

堆栈是对象.在Lua中,对象受垃圾回收.当解释器进入作用域时,它会分配一个堆栈对象并将其推送.只要堆栈对象被推到s-stack上,它就不会被破坏.堆栈堆栈指的是对象.但是,一旦解释器退出作用域,它就会从s-stack中弹出堆栈.因此,由于它不再被引用,因此需要收集.

但是,访问其自身本地范围之外的变量的函数仍然可以引用该堆栈.当Lua编译器看到对local不在函数本地范围内的变量的引用时,它会改变函数.它确定了它所引用的本地所属的堆栈,然后将该堆栈存储为函数中的upvalue.它将对该变量的引用转换为该特定upvalue的偏移量,而不是当前在s-stack上的堆栈的偏移量.

因此,只要函数对象继续存在,它引用的堆栈也将继续存在.

请记住,当Lua解释器进入和退出函数范围时,会动态创建和销毁堆栈.因此,如果您要运行test两次,通过调用loadfile并执行两次返回的函数,您将获得两个单独的函数,这两个函数引用两个单独的堆栈.两个函数都不会看到另一个函数的值.

请注意,这可能不完全是如何实现的,但这是它背后的一般想法.