如何为C中实现的解释语言提供垃圾收集?

时间:2015-02-09 16:21:02

标签: c garbage-collection interpreter

如果我要在C中实现垃圾收集的解释语言,我怎样才能在不编写自己的垃圾收集器的情况下提供精确(即不保守)的垃圾收集?有可用的库吗?如果是这样,哪些?我知道我必须在垃圾收集器跟踪的任何对象上维护某些不变量。

3 个答案:

答案 0 :(得分:4)

如果你想要一个精确的 GC(不是保守的那个,比如Boehm's GC,它在实践中表现很好),你应该跟踪本地指针(到GC-ed数据)变量,或者,如果你确定没有这样的局部变量(btw,GCC编译器有这样的mark&sweep garbage collector - 并且由一些专门的{{{{}}生成标记例程,则只调用几乎空的调用栈。 1}} C ++代码生成器;仅在传递之间调用GGC。当然,您还应该跟踪全局(包括静态或线程本地)指针(到GC-ed数据)变量。

或者,有一些字节码虚拟机(如OCamlNekoVM),然后本地GC-ed变量是字节码VM的堆栈和/或寄存器中的变量,并触发您的GC在VM解释器的特定和精心选择的点上。 (参见Ocaml GC的this explanation)。

您应该阅读有关Garbage Collection技术的更多信息,请参阅GC handbook

如果你的GC正在复制世代,你需要实现写屏障(处理指向新区域的旧数据的突变)。您可以使用旧的Qish GC(我不再维护它)或Ravenbrook's MPS,或编写您自己的世代复制GC(理论上这并不难,但调试GC是一个实践中的噩梦,所以这是很多工作。)

您可能想要使用一些宏技巧(比如我的Qish)来帮助保留您的局部变量。请参阅Ocaml文档的Living in harmony with the garbage collector部分作为示例(或查看Qish内部)。

请注意,在手动编写的C代码中,生成复制GC并不友好(因为您需要显式保留本地指针,并且因为您需要写入屏障来记住修改旧值以指示指向新一代)。如果你想这样做,你的C代码应该在A-normal form中(你不能代码gengtype但你需要编码x=f(g(y),z);并添加temp=g(y); x=f(temp,z);作为局部变量,假设tempxy是本地GC变量,zf都返回GC-ed指针)。实际上,生成C代码要容易得多。请参阅我的MELT域特定语言(以扩展和自定义GCC)作为示例。

如果您的语言是真正多线程的(多个mutator线程并行分配),那么编写GC变得相当棘手。可能需要几个月的工作时间(可能是调试的噩梦)。

实际上,我今天建议使用Boehm的GC(注意它是多线程友好的)。一个天真的标记和扫描手动编码的GC可能不会比Boehm的GC更快。并且你将无法(我不建议)使用GGC(GCC内部的垃圾收集器(IMNSHO,它不是很好;多年前它是一个脏的黑客设计)。

顺便说一下,您可以考虑自定义 -e.g.使用MELT - GCC编译器(通过添加一些特定于应用程序的g__attribute__)来帮助您的GC。通过一些工作,您可以生成一些标记例程等。但是,这种方法可能非常痛苦(我真的不知道)。请注意,MELT(自由软件,GPLv3 +)包含复制世代GC,其旧一代是GGC堆,因此您至少可以查看code of melt-runtime.cc

PS。我还推荐Queinnec的书:Lisp In Small Pieces;它有一些关于GC及其与编程语言的连接的有趣材料,当你实现一个解释器时,它是一本很好的书。斯科特关于Programming Languages Pragmatics的书也值得一读。

答案 1 :(得分:3)

对于C程序,有2个选项:Boehm GC替换malloc(它是保守的 GC,所以可能不完全是您正在寻找的但是它&& #39; 或...),或编写自己的

但写下你自己并不难。执行标记扫描算法。用于标记的根集将是您的符号表。并且您需要另一个表或链接列表来跟踪可以free d的所有已分配内存。当您通过分配列表扫描时,free任何没有标记的内容。

实际编码当然会更复杂,因为你必须遍历这两种数据结构,但算法本身非常简单。你可以做到。

几年前,我发现自己处于相同的搜索状态,这些结果(和AFAIK仍然是)。写自己的作品是非常有益的,值得的。

在实践中,当Basile的答案触及时,会出现许多其他问题。

如果从调用堆栈的深处调用垃圾收集器(通过可能需要更多内存的分配例程),那么必须注意其句柄仍然保存在C函数的局部变量中的任何分配。调用堆栈,而不是保存到其符号表或数据库位置。在我的postscript解释器中,我通过使用所有分配器推送到的临时堆栈来解决这个问题。在所有子程序返回后,主循环清除此堆栈,并在标记期间将其视为根集的一部分。在我的APL解释器中,我每次都在主循环周围调用GC。对于小语言的小程序,速度问题不如更可怕的内存泄漏,至少在影响我的圈子中。

答案 2 :(得分:1)

在实现这种语言时,您的口译员需要跟踪其运行的程序中的所有对象,包括其类型的知识以及数据的哪个部分是对其他数据的引用。然后,您可以轻松地遍历所有数据并实现您喜欢的任何类型的垃圾收集器。没有虚假的黑客像试图确定C实现的堆积" /"堆栈" /等。找到或猜测可能需要什么指针,因为你正在处理你知道其结构的数据。