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

Programming languages 为什么尾部调用优化需要垃圾收集?,programming-languages,garbage-collection,tail-call-optimization,Programming Languages,Garbage Collection,Tail Call Optimization,为什么尾部调用优化需要垃圾收集?是不是因为如果你在一个函数中分配内存,然后你想对它进行尾部调用,那么就没有办法进行尾部调用并重新获得内存?因此,必须保存堆栈,以便在尾部调用之后,可以回收内存。您从哪里听说的 即使没有任何垃圾收集器的C编译器也能够优化尾部递归调用,使其达到迭代等价 你在哪里听到的 即使没有任何垃圾收集器的C编译器也能够优化尾部递归调用,使其达到迭代等价 尾部调用优化不需要垃圾收集 调用堆栈上分配的任何变量都将在递归调用中重用,因此没有内存泄漏 无论是否使用尾部调用优化,在堆上分配

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

您从哪里听说的


即使没有任何垃圾收集器的C编译器也能够优化尾部递归调用,使其达到迭代等价

你在哪里听到的


即使没有任何垃圾收集器的C编译器也能够优化尾部递归调用,使其达到迭代等价

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

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


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

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

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


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

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

但是,假设您有1GB的RAM,并且希望过滤900MB的整数列表,以仅保留正整数。假设一半是正的,一半是负的

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

list *result = filter(make900MBlist(), funcptr);
list *srclist = make900MBlist();
list *result = filter(srclist, funcptr);
freelist(srclist);
make900MBlist将递增GCd,因为已通过的部件过滤器不再被任何对象引用

在没有GC的语言中,要保留尾部递归,必须执行以下操作:

list *result = filter(make900MBlist(), funcptr);
list *srclist = make900MBlist();
list *result = filter(srclist, funcptr);
freelist(srclist);
在最终释放srclist之前,这将不得不使用900MB+450MB,因此程序将耗尽内存并失败

如果您编写自己的筛选器_reclain,则不再需要输入列表时会释放输入列表:

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

它将不再是尾部递归的,并且很可能会使堆栈溢出。

没错,尾部调用优化实际上不需要垃圾收集

但是,假设您有1GB的RAM,并且希望过滤900MB的整数列表,以仅保留正整数。假设一半是正的,一半是负的

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

list *result = filter(make900MBlist(), funcptr);
list *srclist = make900MBlist();
list *result = filter(srclist, funcptr);
freelist(srclist);
make900MBlist将递增GCd,因为已通过的部件过滤器不再被任何对象引用

在没有GC的语言中,要保留尾部递归,必须执行以下操作:

list *result = filter(make900MBlist(), funcptr);
list *srclist = make900MBlist();
list *result = filter(srclist, funcptr);
freelist(srclist);
在最终释放srclist之前,这将不得不使用900MB+450MB,因此程序将耗尽内存并失败

如果您编写自己的筛选器_reclain,则不再需要输入列表时会释放输入列表:

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

它将不再是尾部递归,并且很可能会溢出堆栈。

像大多数神话一样,这一点可能有一点道理。虽然尾部调用优化不需要GC,但在某些情况下它肯定会有所帮助。假设在C++中有类似的东西:

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

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

    return foo(someNumber);
}

如果您有GC,编译器就不必向自由条插入指令。因此,此函数可以优化为尾部调用。

与大多数神话一样,此函数可能有一点道理。虽然尾部调用优化不需要GC,但在某些情况下它肯定会有所帮助。假设在C++中有类似的东西:

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

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

    return foo(someNumber);
}

如果您有GC,编译器就不必向自由条插入指令。因此,可以将此函数优化为尾部调用。

明显的问题:谁声称尾部调用优化需要垃圾收集?明显的问题:谁声称尾部调用优化需要垃圾收集?我希望看到一个参考,或一些支持这一点的东西。例如,我知道GCC支持这个优化——但是我不知道它如何处理需要清理的C++对象。我很想看到一个引用,或者有什么东西可以支持这个。我知道GCC支持这个优化——但是我不知道它如何处理需要清理的C++对象。+ 1:对尾部调用很有意思,优化RAII,转换成一个UNIQUY的PTR。
如果不是作为参数传递,则在尾部调用之前释放,否则在被调用方中释放。@JonHarrop:通常的解决方法是简单地将主体包装在{}中,并在外部返回。+1:尾部调用优化RAII会很有趣,转换为类似于唯一的\u ptr的东西,如果它没有作为参数传递或在被调用方中解除分配,那么它将在尾部调用之前解除分配。@Jonharop:通常的解决方法是简单地将主体包装在{}中,并在外部返回。C没有RAII,也就是说,尾部调用实际上是尾部调用。在C++中,一些变量在表达式递归调用完成后就结束了范围,这将阻止这种优化,因为需要清理代码,而不是真正的尾部调用。C没有RAII,即尾部调用真的是尾部调用。在C++中,一些变量在表达式递归调用完成后就结束了范围,这将阻止这种优化,因为需要清理运行的代码,而不是真正的尾部调用。