以下段落来自The Racket Guide (2.3.4):
同时,递归并不会导致特别糟糕 在Racket中表现,并没有堆栈溢出这样的东西; 如果计算涉及太多的上下文,你可能会耗尽内存, 但是耗尽内存通常需要更深的数量级 递归比在其他语言中触发堆栈溢出。
我很想知道如何设计Racket以避免堆栈溢出?更何况,为什么像C这样的其他语言无法避免这样的问题?
答案 0 :(得分:3)
首先,一些术语:进行非尾调用需要上下文框来存储局部变量,父返回地址等。所以问题是如何表示任意大的上下文。 "堆栈" (或调用堆栈)只是上下文的一个(公认的常见)实现策略。
以下是深度递归(即大型上下文)的一些实现策略:
mprotect
或其他操作来说服操作系统新的内存块可以作为调用堆栈处理。)支持深度递归通常与对一流延续的支持相吻合。通常,实现一流的延续意味着您几乎可以自动获得对深度递归的支持。 Will Clinger等人发表了一篇名为this的好文章。更多细节和不同策略之间的比较。
答案 1 :(得分:2)
这个答案有两个部分。
首先,在Racket和其他函数语言中,尾调用不会创建额外的堆栈帧。也就是说,一个循环,如
(define (f x) (f x))
...可以永远运行而不使用任何堆栈空间。许多非函数式语言没有像函数式语言那样优先考虑函数调用,因此没有正确地进行尾调用。
但是,您所指的评论不仅限于尾部呼叫; Racket允许非常深层嵌套的堆栈帧。
您的问题很好:为什么其他语言不允许深层嵌套的堆栈帧?我写了一个简短的测试,看起来C毫不客气地将核心转储到262,000到263,000之间。我写了一个简单的球拍测试做同样的事情(小心确保递归调用不在尾部位置),并且我在48,000,000深度中断它而没有任何明显的不良影响(除了,可能是一个相当大的运行时堆栈) )。
直接回答你的问题,没有理由我知道C不能允许更深层次的嵌套堆栈,但我认为对于大多数C程序员来说,递归深度为262K是足够的。
不适合我们!
这是我的C代码:
#include <stdio.h>
int f(int depth){
if ((depth % 1000) == 0) {
printf("%d\n",depth);
}
return f(depth+1);
}
int main() {
return f(0);
}
...和我的球拍代码:
#lang racket
(define (f depth)
(when (= (modulo depth 1000) 0)
(printf "~v\n" depth))
(f (add1 depth))
(printf "this will never print..."))
(f 0)
编辑:这是在出路上使用随机性阻止可能的优化的版本:
#lang racket
(define (f depth)
(when (= (modulo depth 1000000) 0)
(printf "~v\n" depth))
(when (< depth 50000000)
(f (add1 depth)))
(when (< (random) (/ 1.0 100000))
(printf "X")))
(f 0)
另外,我对进程大小的观察结果与大约16字节的堆栈帧一致,加上或减去; 50M * 16字节= 800兆字节,观察到的堆栈大小约为1.2千兆字节。