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
声明的任何变量,因为它们不在块的范围内.yes
no
no
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
并执行两次返回的函数,您将获得两个单独的函数,这两个函数引用两个单独的堆栈.两个函数都不会看到另一个函数的值.
请注意,这可能不完全是如何实现的,但这是它背后的一般想法.
归档时间: |
|
查看次数: |
3812 次 |
最近记录: |