Memory management 何时释放关闭';lisp解释器中的内存

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

我正在从头开始编写一个简单的lisp解释器。我有一个全局环境,在对文件中的所有表单求值期间,顶级变量都会绑定到该环境中。对文件中的所有表单求值后,将释放顶级env及其内部的所有键值数据结构

当计算器遇到
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帧相关联的内存,并支持这种持久的高阶函数

我可以想出一些解决办法:

  • 某种类型的弧

  • 在生成封闭环境时,将其复制到已评估过程的框架中

这似乎就是所暗示的。因此,与其在PROC对象中保存指向其闭包的指针,我将。。。复制闭包环境并在单元格中直接存储指向该环境的指针?这难道不只是把问题踢得更深一层而导致同样的问题吗

  • 在读取时在高阶函数体内部递归替换标签
我担心我在这里可能会遗漏一些非常简单的东西,同时我也很好奇这个过程是如何在lisp的其他实现中得到支持的,以及通常使用闭包的其他语言。我没有太多的运气去寻找答案,因为这个问题非常具体,甚至可能是这个实现(我承认,作为一个学习项目,我只是脱帽而出),我能找到的很多东西只是从正在实现的语言的角度解释了闭包的细节,不是从语言中实现该语言的


如果有帮助,请参阅我的资料中的相关行。如果这个问题不够详细,无法彻底描述问题,我很乐意详细说明。谢谢

在朴素的解释器中通常处理这种情况的方法是使用垃圾收集器(GC)并在GC的堆中分配激活帧。因此,您从不显式释放这些帧,而是让GC在适用时释放它们

在更复杂的实现中,您可以使用稍微不同的方法:

  • 创建闭包时,不要存储指向当前环境的指针。相反,复制闭包使用的那些变量的值(称为lambda的自由变量)。 并更改闭包的主体以使用这些副本,而不是在环境中查找这些变量。这叫做闭包转换
  • 现在,您可以将环境视为普通堆栈,并在退出作用域后立即释放激活帧
  • 您仍然需要GC来决定何时可以释放闭包
  • 这反过来需要一个“赋值转换”:如果这些变量被修改,复制变量的值意味着语义的改变。因此,要恢复原始语义,您需要查找那些“复制到闭包”以及“修改”的变量,并将它们转换为“参考单元格”(例如,将值保存在
    car
    中的cons单元格),以便副本不再复制值,但只需将引用复制到保存该值的实际位置即可。[旁注:这样的实现显然意味着避免
    setq
    并使用更具功能性的风格可能最终会更有效。]

更复杂的实现还有一个优势,即它可以提供安全的空间语义:闭包将只保留它实际引用的数据,与天真的方法相反,闭包最终会引用整个周围环境,因此会阻止GC收集实际未被引用但恰好在闭包捕获时位于环境中的数据。

我可能遗漏了一些东西,但你说“在对PROC主体进行评估后,我可以释放与它相关的所有单元,包括其框架环境,并在没有内存泄漏的情况下退出。”。您不能多次重用闭包吗?在这种情况下,您将过早释放环境。这正是问题所在