2015-11-21 158 views
1

我产生随机数的列表:生成列表导致堆栈溢出

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进行调试?

(元:我应该张贴它一个单独的线程,或者我应该留在这里)

+0

您正在使用哪种配置? (在T40,1GB,Ubuntu 12.0x -utop版本1.18.1(使用OCaml版本4.02.1);没有问题,甚至有10多个,让lo = gen 10000000 ;;)。 –

+0

华硕R556LB,8G内存,薄荷17.2肉桂,OCaml 4.01 – marmistrz

+0

我有堆栈溢出不在gen,但在quick_sort。 –

回答

2

由于错误是在不同的函数中,因此以下是您如何在将来更快地调试这种事情。

  1. 您可以打开回溯。请在程序开始时调用Printexc.record_backtrace true,或者使用设置为b的环境变量OCAMLRUNPARAM运行它,如OCAMLRUNPARAM=b ./a.out中所述。这应该告诉你错误发生在哪里,尽管有时它会跳过你期望成为调用堆栈的部分内容 - 我相信这是由于优化如内联。不过,它通常很有用。为此,该程序必须编译为国旗-g

  2. 通过执行二分搜索,您仍然可以找到异常的来源,即使在程序中的一堆函数调用中也是如此。首先在处理程序中包装函数调用的一半。如果发生异常,请打开它们,然后将其中一半重新放入处理程序中,依此类推,直至深入到源代码。它还是有点费力,但你可以调试这种方式在O(log n)的时间,以及数不胜数的日志并不多:)

+0

请注意@ivg从类似的出发点到这个有更多的明确答案。请看它,而不是如果你打算使用回溯来调试。 – antron

0

我跑在我的Mac Pro的代码,并没有得到一个堆栈溢出。正如你所说你的代码看起来非常好。

可能的解释:

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

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

更新

我认为@antron有一个良好的出发点。如果你正在运行编译代码,你很可能不知道问题出在哪里。添加一些跟踪,你很可能会发现堆栈溢出在其他地方。

+0

用OCamlMakefile(ocamlc)编译我的代码。整个代码在这里:http://paste.ubuntu.com/13403578/ – marmistrz

+0

它的ulimit说:http://paste.ubuntu.com/13404591/ – marmistrz

+2

你确定它不是其他功能之一那会导致堆栈溢出? – antron

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 

宾果。问题的根源是指向一个调用点。

注意:OCAMLRUNPARAMl选项只能被字节码运行时所理解。为了对本地代码重复相同的技巧,应该使用操作系统提供的机制来限制堆栈。对于通常,它通常是ulimit -s外壳基元。