为什么尾部调用优化需要垃圾收集?

时间:2008-12-05 17:15:06

标签: programming-languages garbage-collection tail-call-optimization

为什么尾部调用优化需要垃圾收集?是因为如果你在一个你想要进行尾调用的函数中分配内存,那么就没有办法进行尾调用并重新获得那个内存? (因此必须保存堆栈,以便在尾调用后,可以回收内存。)

4 个答案:

答案 0 :(得分:9)

像大多数神话一样,这个可能有一点道理。虽然GC不是必需用于尾部调用优化,但在少数情况下它肯定帮助。假设你在C ++中有这样的东西:

int foo(int arg) {
    // Base case.

    vector<double> bar(10);
    // Populate bar, do other stuff.

    return foo(someNumber);
}

在这种情况下,返回foo(someNumber); 看起来像<尾部调用,但由于你正在使用RAII内存管理,并且你必须自由禁止,这一行将转换为更低级别,如下所示(非正式伪代码):

ret = foo(someNumber);
free(bar);
return ret;

如果您有GC,则编译器无需将指令插入空闲栏。因此,此函数可以优化为尾调用。

答案 1 :(得分:7)

你在哪里听到的?

即使没有任何垃圾收集器的C编译器也能够优化对迭代等效的尾递归调用。

答案 2 :(得分:4)

尾部调用优化不需要垃圾收集。

在调用堆栈上分配的任何变量都将在递归调用中重用,因此那里没有内存泄漏。

无论是否使用尾调用优化,在堆调用之前分配的任何局部变量都不会在尾调用之前释放。无论是否使用尾调用优化,在堆调用之前分配并在尾调用之前释放的局部变量都不会泄漏内存。

答案 3 :(得分:2)

确实,尾部调用优化并不真正需要垃圾收集。

但是,假设你有1 GB的RAM,你想要过滤900MB的整数列表,只保留正数。假设大约一半是正面的,一半是负面的。

在使用GC的语言中,您只需编写该函数即可。 GC会发生很多次,你最终会得到一个450 MB的列表。代码如下所示:

list *result = filter(make900MBlist(), funcptr);

make900MBlist将逐步GCd,因为部件过滤器已经过去不再被任何东西引用。

在没有GC的语言中,为了保留尾递归,你必须做这样的事情:

list *srclist = make900MBlist();
list *result = filter(srclist, funcptr);
freelist(srclist);

在最终释放srclist之前,最终必须使用900MB + 450MB,因此程序将耗尽内存并失败。

如果您编写自己的filter_reclaim,则不再需要释放输入列表:

list *result = filter_reclaim(make900MBlist(), funcptr);

它不再是尾递归的,你可能会溢出你的堆栈。