Memory management 何时释放关闭';lisp解释器中的内存
我正在从头开始编写一个简单的lisp解释器。我有一个全局环境,在对文件中的所有表单求值期间,顶级变量都会绑定到该环境中。对文件中的所有表单求值后,将释放顶级env及其内部的所有键值数据结构 当计算器遇到Memory management 何时释放关闭';lisp解释器中的内存,memory-management,lisp,interpreter,Memory Management,Lisp,Interpreter,我正在从头开始编写一个简单的lisp解释器。我有一个全局环境,在对文件中的所有表单求值期间,顶级变量都会绑定到该环境中。对文件中的所有表单求值后,将释放顶级env及其内部的所有键值数据结构 当计算器遇到lambda表单时,它会创建一个PROC对象,该对象包含三个内容:应用过程时要绑定到本地帧中的参数列表、函数体以及指向创建它的环境的指针。例如: (λ(x)x) 会在内部产生类似于: PROC- args: x, body: x, env: pointer to to
lambda
表单时,它会创建一个PROC
对象,该对象包含三个内容:应用过程时要绑定到本地帧中的参数列表、函数体以及指向创建它的环境的指针。例如:
(λ(x)x)
会在内部产生类似于:
PROC- args: x,
body: x,
env: pointer to top level env
当应用PROC
时,将为框架创建一个新的环境,并在其中暂存本地绑定,以允许使用适当的绑定评估主体。此框架环境包含一个指向其闭包的指针,以允许在该闭包中查找变量。在这种情况下,这将是全球环境。在对PROC
主体求值之后,我可以释放与它相关的所有单元,包括它的帧环境,并在没有内存泄漏的情况下退出
我的问题是高阶函数。考虑这一点:
(定义conser
(λ(x)
(兰姆达(y)(cons x y)))
一个函数,它接受一个参数并生成另一个函数,该函数将把该参数转换为您传递给它的内容。所以
(定义用户(conser)(1)))
将生成一个函数,该函数对传递给它的任何内容进行约束(1)。例:
(aconser'(2));((1) 2)
我这里的问题是,acoser
必须保留一个指向创建它的环境的指针,即通过调用(conser'(1))
生成is时的conser
。当应用aconser
程序时,其框架必须指向定义aconser
时存在的conser
框架,因此应用后无法释放conser
框架。我不知道在应用lambda帧时如何/最好地释放与lambda帧相关联的内存,并支持这种持久的高阶函数
我可以想出一些解决办法:
- 某种类型的弧
- 在生成封闭环境时,将其复制到已评估过程的框架中
- 在读取时在高阶函数体内部递归替换标签
如果有帮助,请参阅我的资料中的相关行。如果这个问题不够详细,无法彻底描述问题,我很乐意详细说明。谢谢 在朴素的解释器中通常处理这种情况的方法是使用垃圾收集器(GC)并在GC的堆中分配激活帧。因此,您从不显式释放这些帧,而是让GC在适用时释放它们 在更复杂的实现中,您可以使用稍微不同的方法:
- 创建闭包时,不要存储指向当前环境的指针。相反,复制闭包使用的那些变量的值(称为lambda的自由变量)。 并更改闭包的主体以使用这些副本,而不是在环境中查找这些变量。这叫做闭包转换
- 现在,您可以将环境视为普通堆栈,并在退出作用域后立即释放激活帧
- 您仍然需要GC来决定何时可以释放闭包
- 这反过来需要一个“赋值转换”:如果这些变量被修改,复制变量的值意味着语义的改变。因此,要恢复原始语义,您需要查找那些“复制到闭包”以及“修改”的变量,并将它们转换为“参考单元格”(例如,将值保存在
中的cons单元格),以便副本不再复制值,但只需将引用复制到保存该值的实际位置即可。[旁注:这样的实现显然意味着避免car
并使用更具功能性的风格可能最终会更有效。]setq
更复杂的实现还有一个优势,即它可以提供安全的空间语义:闭包将只保留它实际引用的数据,与天真的方法相反,闭包最终会引用整个周围环境,因此会阻止GC收集实际未被引用但恰好在闭包捕获时位于环境中的数据。我可能遗漏了一些东西,但你说“在对PROC主体进行评估后,我可以释放与它相关的所有单元,包括其框架环境,并在没有内存泄漏的情况下退出。”。您不能多次重用闭包吗?在这种情况下,您将过早释放环境。这正是问题所在