生成列表会导致堆栈溢出

时间:2015-11-21 18:07:56

标签: ocaml stack-overflow tail-recursion

我正在生成一个随机数列表:

let gen n =
    let rec pom l n =
        match n with
        | 0 -> l
        | _ ->
            let el = Random.int 20000000
            in pom (el::l) (n-1)
    in
        pom [] n

let lo = gen 1000000

我得到的是

Fatal error: exception Stack_overflow

为什么呢?我正在使用尾递归(和累加器)

修改

你是对的,堆栈溢出了两种类型。

但是如果我的代码有很多行,那么以这种方式调试它会很痛苦。我想在这里使用ocamldebug,就像学习经验一样。我用这种方式运行ocamldebug:

(ocd) r
Loading program... done.
Time: 88089944
Program end.
Uncaught exception: Stack_overflow
(ocd) b
Time: 88089943 - pc: 52 - module Pervasives
214   | hd :: tl -> <|b|>hd :: (tl @ l2)
(ocd) bt
#0  Pc: 52  Pervasives char 7700
#1  Pc: 64  Pervasives char 7715
#2  Pc: 64  Pervasives char 7715
#3  Pc: 64  Pervasives char 7715
#4  Pc: 64  Pervasives char 7715
#5  Pc: 64  Pervasives char 7715
#6  Pc: 64  Pervasives char 7715
// and so it goes on forever

这告诉我为什么我的程序崩溃了。我怎么能用ocamldebug调试它?

(meta:我应该为它发布一个单独的帖子,还是应该留在这里)

3 个答案:

答案 0 :(得分:2)

由于错误的功能不同,以下是您将来如何更快地调试此类事件。

  1. 您可以开启回溯。您可以在程序开头调用Printexc.record_backtrace true,也可以将环境变量OCAMLRUNPARAM设置为b来运行,如OCAMLRUNPARAM=b ./a.out中所示。这应该告诉你错误发生在哪里,虽然有时它会跳过你希望调用堆栈的部分内容 - 我相信这是由于内联等优化所致。但它通常很有用。为此,程序必须使用标志-g

  2. 进行编译
  3. 通过二进制搜索,您仍然可以找到异常的来源,即使在程序中的一堆函数调用中也是如此。首先将一半函数调用包装在处理程序中。如果在那里发生异常,请打开它们,然后再将其中的一半包裹在处理程序中,依此类推,直到您深入查看源代码。它仍然有点劳动密集型,但您可以在O(log n)时间内以这种方式进行调试,并且数据的日志不是那么多:)

答案 1 :(得分:1)

打印Stack_overflow异常的回溯通常有些无用,因为导致溢出的调用数超过了回溯缓冲区的大小。例如,如果您采用以下程序(backtrace.ml):

let init n =
  let rec loop xs x =
    if x >= 0 then loop (x::xs) (x-1) else xs in
  loop [] (n-1)

let sum = function
  | [] -> 0
  | x :: xs -> List.fold_right (+) xs x

let () =
  let xs = init 10000000 in
  let y = sum xs in
  print_int y

并使用

执行它
 OCAMLRUNPARAM=b ocamlbuild backtrace.d.byte -- 

你会得到一个无用的表格回溯:

Fatal error: exception Stack_overflow
Raised by primitive operation at file "list.ml", line 89, characters 16-37
Called from file "list.ml", line 89, characters 16-37
Called from file "list.ml", line 89, characters 16-37
...

我们不能增加内部回溯缓冲区,但我们可以减小堆栈大小,从而限制回溯的大小,因此它可以适应缓冲区。因此,如果我们在有限的堆栈中运行程序,我们将获得更好的回溯:

OCAMLRUNPARAM="b,l=100" ocamlbuild backtrace.d.byte --
Fatal error: exception Stack_overflow
Raised by primitive operation at file "list.ml", line 89, characters 16-37
Called from file "list.ml", line 89, characters 16-37
Called from file "list.ml", line 89, characters 16-37
...
Called from file "backtrace.ml", line 18, characters 10-16

宾果。问题的根源被精确定位到呼叫站点。

注意:l的{​​{1}}选项只能由字节码运行时理解。为了对本机代码重复相同的技巧,应该使用操作系统提供的机制来限制堆栈。对于unices,它通常是OCAMLRUNPARAM shell原语。

答案 2 :(得分:0)

我在Mac Pro上运行了代码,但没有出现堆栈溢出。正如你所说,你的代码看起来非常好。

可能的解释:

  1. 您在内存有限的环境中运行。

  2. 您对代码的某些部分有一些旧的定义。也许尝试重新运行一个新的OCaml toplevel。

  3. <强>更新

    我认为@antron有一个很好的观点。如果您正在运行已编译的代码,则很可能无法确切地知道问题所在。添加一些跟踪,你很可能会发现堆栈溢出在其他地方。