我知道Scheme是尾递归的,但如果一个函数在let绑定中调用自己,那么绑定值是否会保留在内存中,即使它们不需要?我有类似的代码:
(define (some-function)
(let ((c (read-char)))
(some-function)))
实际功能有点复杂,但由于流程等因素相同,因此无关紧要。因为从let块内部调用函数,是否会保留'c'的值?如果这个功能持续一段时间,我不想浪费大量的额外内存。
答案 0 :(得分:2)
如果一个函数是尾递归的,那么如果递归函数调用是作为函数的一部分计算的最后一个东西,在你的代码就是这种情况,递归不会在内存中保留任何局部变量,所以栈空间消耗保持不变。
如果它不是递归递归,那么如果在递归之后仍然有一些事情要做,那么c
必须保留在内存中,使得函数消耗O(n)
堆栈空间其中n
是递归的数量。一个例子就是:
(define (some-function)
(let ((c (read-char)))
(+ c (some-function))))
这里将在递归之后执行添加,因此在执行递归调用时必须将c
保留在内存中。
答案 1 :(得分:2)
考虑你的例子:
(define (some-function)
(let ((c (read-char)))
; <HERE>
(some-function)))
(some-function)
假设我们运行此程序。该程序将一遍又一遍地循环调用some-function
。在某些时候,垃圾收集器被激活。假设为了参数,进程(正在运行的程序)在标记为<here>
的点上被中断。
垃圾收集器的工作是回收不再使用的内存。 “使用中”是什么意思?
正在使用一块内存,如果在程序继续时可能会使用它(或占用内存的值)。名称c
绑定到一个字符,因此无法回收该字符。但是,由于对some-function
的调用是尾递归的,因此一次只调用一次some-function
。这意味着可以回收c
的所有旧值。
对于循环函数尾部调用的命名循环,仅使用最新的值。 (这些值会保留在内存中,直到垃圾收集器被激活为止。)
一个小实验,试试:
(let loop ((v 0))
(loop (make-vector 100000 42))
这个程序循环和循环。对于每一轮,分配长度为100000的向量。如果内存不足,您就知道v
的值不是垃圾回收。如果程序不间断运行,那么垃圾收集器就可以回收内存。
答案 2 :(得分:1)
在尾部位置,绑定变量不需要存在。在呼叫时,只有创建过程时可用的变量才可用。
(define (some-function)
;; only global variables exist at this point
(let ((c (read-char)))
;; c exists for a brief time
(some-function)))
在过程中评估变量some-function
之后,在应用评估的c
之前删除some-function
。一次只存在一个c
。
想象一下这个稍微改动的版本:
(define (some-function previous)
(let ((c (read-char)))
(some-function c)))
除了some-function
之前评估c
和c
以及previous
被删除且申请已开始,我们才会对此进行评估。
要求以这种方式处理过程和变量。它被称为tail call optimization。
创建过程时,过程中使用的变量需要在过程存在期间存在:
(define (some-function2)
(let ((c (read-char)))
(lambda () c)))
(define test (some-function2))
(define test2 (some-function2))
现在。当some-function2
调用完成时,变量将被移动到与过程相关的内容,并且仍然不是环境的一部分。这些过程称为闭包,每个过程中的c
都是闭包变量,即使在绑定过程完成后也会存在。如果你习惯了其他语言,你可以想到这些变量存在于堆上。这些&#34;自由变量&#34;是什么让它在70年代创建时非常特别,而且每种编程语言都或多或少地基于这个原则。