Recursion 读取深度嵌套的树会导致堆栈溢出

Recursion 读取深度嵌套的树会导致堆栈溢出,recursion,lisp,common-lisp,stack-overflow,s-expression,Recursion,Lisp,Common Lisp,Stack Overflow,S Expression,我试图将一个大容量的sexp从文件读入内存,对于较小的输入似乎效果不错,但对于嵌套较深的输入,sbcl会因堆栈耗尽而失效。似乎存在一个sbcl根本无法超越的硬递归限制(1000个函数深)(奇怪的是,即使堆栈大小增加)。示例(代码为):make check-c工作,但make check-cpp如下所示排出烟囱: INFO: Control stack guard page unprotected Control stack guard page temporarily disabled: pro

我试图将一个大容量的sexp从文件读入内存,对于较小的输入似乎效果不错,但对于嵌套较深的输入,sbcl会因堆栈耗尽而失效。似乎存在一个sbcl根本无法超越的硬递归限制(1000个函数深)(奇怪的是,即使堆栈大小增加)。示例(代码为):
make check-c
工作,但
make check-cpp
如下所示排出烟囱:

INFO: Control stack guard page unprotected
Control stack guard page temporarily disabled: proceed with caution
Unhandled SB-KERNEL::CONTROL-STACK-EXHAUSTED in thread #<SB-THREAD:THREAD
                                                         "main thread" RUNNING
                                                          {10034E6DE3}>:
  Control stack exhausted (no more space for function call frames).
This is probably due to heavily nested or infinitely recursive function
calls, or a tail call that SBCL cannot or has not optimized away.

PROCEED WITH CAUTION.

Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {10034E6DE3}>
0: ((LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX))
1: (SB-IMPL::CALL-WITH-SANE-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {100FC9006B}>)
2: (SB-IMPL::%WITH-STANDARD-IO-SYNTAX #<CLOSURE (LAMBDA NIL :IN SB-DEBUG::FUNCALL-WITH-DEBUG-IO-SYNTAX) {100FC9003B}>)
...
然后在sbcl中:

(with-open-file (file "file" :direction :input) (read file))
同样的故障也会发生

编辑:在
#sbcl
上四处询问,显然控制堆栈大小实际上只适用于新线程,并且主线程的堆栈大小也受到许多其他因素的影响。所以我试着把阅读放在一个单独的线程中。还是不行。签出并运行
make check
,如果您感兴趣。

我不知道您做了什么(因为您没有准确显示),但当我按如下方式启动
sbcl
时,您的示例对我来说很好:

sbcl --control-stack-size 100
当然,我推荐GNU CLISP和Embedded Common Lisp,因为它们在您的示例中也可以正常工作

我将为未来的读者添加此答案的参考:


我还将提到,在许多CL实现中,可能需要使用适当的优化选项编译代码,以从尾部调用优化中获益。

递归函数是解析递归数据格式(如S表达式)的自然方式。由于Lisp读取宏的工作方式,它实际上是必需的。诚然,以迭代方式执行它会更加困难,但由于似乎没有一种方法可以绕过递归读取的堆栈限制,因此我看不到以递归方式正确解析它的方法。不过,我正在寻找的解析器可能是尾部递归的。您当然可以编写这样的自定义解析器,特别是如果您的输入不像
read
需要处理的那样通用。但是没有任何内置的东西可以这样工作。可能没有内置的,是的。我在寻找一些可能鲜为人知的外部库,因为解析深度嵌套的sexps似乎是其他人已经解决过的应用程序(日志结构、序列化等应用程序)。想想看,如果我真的写了一本这样的书,我还不如把它放在一个单独的图书馆里。所以。。。。。为什么使用SBCL而不是一个通用的Lisp来实现这个目的呢????例如,GNU CLISP和可嵌入的Common Lisp都可以轻松地处理您的简单示例。另外,你可以尝试搜索其他有类似问题的帖子,这些帖子可能会有有用的答案,比如:对,问题底部的例子很简单。如问题顶部所述,我正在进行的测试基于链接的github repo。我在底部加了一些解释,以防不清楚。我试过CLISP,它并没有像sbcl那样死掉,但它也没能在半小时内完成(我检查过它没有挂起来),这并不理想。我不得不改变我的整个策略,因为即使read不会破坏堆栈,它仍然是递归的,因此在大的输入上非常慢。好了,现在,你开始了!如果您要求解释器对太多的数据进行有效的无界递归,那么您将有效地调用“未定义的行为”,正如他们所说,这可能意味着任何事情,包括让守护进程飞出您的鼻子。如果您不能使用可预测的预设堆栈限制或尾部递归算法来执行此操作,那么您必须迭代地执行此操作,并且仍然要关注堆的使用情况。更好的是,修复您的数据表示!是的,我问这个问题的唯一原因是因为我觉得很奇怪,
(read)
是(非尾部)递归的,而它是这样一个基本函数,递归效率较低。最后,我通过避免将它弹跳到文本,直接在C++中工作而不涉及Lisp,最终修复了AST解析的数据表示。
sbcl --control-stack-size 100