2010-03-17 33 views
4

Hugs Haskell實現中「Error C stack overflow」的常見原因是什麼?Haskell中「Error C stack overflow」的原因通常爲

+4

哪個實現?HUGS? NHC? GHC? – 2010-03-17 14:02:20

+1

所有的答案似乎都在談論溢出Haskell運行時的堆棧。 C堆棧不同,至少在GHC中。這是一個令人困惑的錯誤消息,或者是與FFI相關的問題。 (我做了很多FFI工作,從來沒有見過這種情況,但我使用GHC)。 – jrockway 2010-03-18 01:17:34

回答

10

如果您習慣於使用尾遞歸因式分解的函數式語言,這可能會出現。假設你有一個函數:

sum = go 0 
    where 
    go accum [] = accum 
    go accum (x:xs) = go (accum+x) xs 

,順便說一下,是一樣的

sum = foldl (+) 0 

如果我們評估的功能,我們可以看到這個問題:

sum [1,2,3,4] 
go 0 [1,2,3,4] 
go (0+1) [2,3,4] 
go ((0+1)+2) [3,4] 
go (((0+1)+2)+3) [4] 
go ((((0+1)+2)+3)+4) [] 
(((0+1)+2)+3)+4 

現在,以評估表達式Haskell使用堆棧:

EXPR   | STACK 
(((0+1)+2)+3)+4 | 
((0+1)+2)+3  | +4 
(0+1)+2   | +3 +4 
(0+1)   | +2 +3 +4 
1    | +2 +3 +4 
3    | +3 +4 
6    | +4 
10    | 

這個是可能發生溢出的地方。如果您評估了總和[1..10^6],那麼該堆棧將會有一百萬個條目。

但請注意這裏的病理。您只是爲了建立一個巨大的fscking表達式(「thunk」)而遞歸列表,然後使用堆棧來評估它。我們寧願評估它,因爲我們遞歸,通過使尾遞歸嚴格:

sum = go 0 
    where 
    go accum [] = accum 
    go accum (x:xs) = accum `seq` go (accum+x) xs 

,上面寫着試圖評估遞歸調用之前評估ACCUM,所以我們得到(這可能需要一定的耐心來了解) :

sum [1,2,3,4] 
go 0 [1,2,3,4] 
let accum = 0 in accum `seq` go (accum+1) [2,3,4] 
go (0+1) [2,3,4] 
let accum = 0+1 in accum `seq` go (accum+2) [3,4] 
go (1+2) [3,4] 
let accum = 1+2 in accum `seq` go (accum+3) [4] 
go (3+3) [4] 
let accum = 3+3 in accum `seq` go (accum+4) [] 
go (6+4) [] 
6+4 
10 

因此,當我們遍歷列表時,我們計算總和,所以我們不必使用深棧來評估結果。這種改進的尾遞歸模式被封裝在Data.List.foldl」,所以:

sum = foldl' (+) 0 

一個好的經驗法則是從不使用與foldl,因爲它總是建立起巨大的thunk。改用foldl'。如果輸出類型具有延遲結構(例如列表或函數),請使用foldr。但是一般來說,避免堆棧溢出沒有銀彈,你只需瞭解你的程序的評估行爲。有時候這可能很難。但是,如果我理解正確,堆棧溢出總是來自嘗試評估一個巨大的thunk,但是。因此,尋找那些可以創建這些地方的地方,並迫使評估更早發生。

+1

+1對foldl的工作原理有很好的解釋 – karoberts 2010-03-17 20:57:33

1

失控遞歸是最有可能的候選人......您需要提供更多信息,以獲得更準確的答案。

1

下面是一些代碼,可能導致失控的遞歸:

main = 
    let x = x :: Int 
    in print x 

這裏會發生什麼事是,當xprint x評估它啓動,然後發現,要完成它需要評估x的評價。

+2

由於TCO(注意x是x中的尾部調用),可能不會導致堆棧溢出。這將是一個無限循環。 – luqui 2010-03-17 14:42:17

+0

嗯...所以它是,luqui。我確信這種代碼在我上次嘗試時產生了堆棧錯誤(不出所料,幾年前)。 – 2010-03-17 15:23:05

+0

糟糕。我是對的,但情況不同。如果我在GHCi中運行該代碼,它就會永遠循環。如果我編譯並運行它,我得到的錯誤:a.out:<> – 2010-03-17 15:24:46

0

最可能的原因必須是不受控制的遞歸。每次遞歸調用都會爲其輸入/輸出參數消耗更多的堆棧空間。

+2

只有當你正確定義「遞歸」時。 Haskell對什麼消耗堆棧空間比大多數函數式語言有一個完全不同的概念。 – luqui 2010-03-17 14:18:27