據我所知,尾遞歸函數在最後一步調用自己(如return語句),但是,函數的第一個實例不會終止,直到所有其他實例都終止,因此在達到多個實例後我們會發生堆棧溢出。鑑於遞歸是最後一步,有什麼辦法在下一個實例期間或之前終止前一個實例嗎?如果一個實例的唯一目的是調用下一個實例,那麼沒有理由將它存放在內存中,對吧?尾遞歸堆棧溢出
Q
尾遞歸堆棧溢出
2
A
回答
2
是的,一些編譯器會優化尾遞歸,因此它不需要額外的堆棧空間。例如,讓我們看一下這個示例的C函數:
int braindeadrecursion(int n)
{
if (n == 0)
return 1;
return braindeadrecursion(n - 1);
}
這個函數很簡單;它只是返回1.如果沒有優化,鐺產生我的機器上的代碼看起來像這樣:
_braindeadrecursion:
00 pushq %rbp
01 movq %rsp,%rbp
04 subq $0x10,%rsp
08 movl %edi,0xf8(%rbp)
0b movl 0xf8(%rbp),%eax
0e cmpl $_braindeadrecursion,%eax
11 jne 0x0000001c
13 movl $0x00000001,0xfc(%rbp)
1a jmp 0x0000002e
1c movl 0xf8(%rbp),%eax
1f subl $0x01,%eax
22 movl %eax,%edi
24 callq _braindeadrecursion
29 movl %eax,%ecx
2b movl %ecx,0xfc(%rbp)
2e movl 0xfc(%rbp),%eax
31 addq $0x10,%rsp
35 popq %rbp
36 ret
正如你所看到的,遞歸調用是在那裏0X24。現在,讓我們嘗試更高的優化:
_braindeadrecursion:
00 pushq %rbp
01 movq %rsp,%rbp
04 movl $0x00000001,%eax
09 popq %rbp
0a ret
現在,看看那個 - 沒有更多的遞歸!這個例子非常簡單,但優化仍然可以發生在更復雜的尾遞歸情況下。
1
可以是: - 可以增加堆棧大小或 - 不要使用遞歸,而是使用某種循環。
相關問題
- 1. 尾遞歸:堆棧溢出
- 2. 堆棧溢出與尾遞歸
- 3. Haskell遞歸堆棧溢出
- 4. 運行尾遞歸方法堆棧溢出
- 5. 當不在本地運行時,F#尾遞歸堆棧溢出
- 6. scala中沒有尾遞歸優化時堆棧溢出?
- 7. 試圖防止堆棧溢出與尾遞歸
- 8. Lisp堆棧溢出遞歸函數
- 9. 通過遞歸導致堆棧溢出
- 10. 遞歸函數中的堆棧溢出
- 11. Java堆棧與遞歸溢出
- 12. 遞歸函數堆棧溢出
- 13. 遞歸方法中的堆棧溢出
- 14. 遞歸函數堆棧溢出
- 15. 遞歸方法堆棧溢出錯誤
- 16. 使用遞歸溢出堆棧
- 17. 堆棧溢出錯誤,無遞歸
- 18. ColdFusion:遞歸太深;堆棧溢出
- 19. 遞歸求和堆棧溢出
- 20. C#填充TreeView遞歸堆棧溢出
- 21. 堆棧溢出和遞歸方法
- 22. 如何避免遞歸堆棧溢出?
- 23. 遞歸 - 堆棧溢出錯誤
- 24. 遞歸函數haskell堆棧溢出
- 25. 遞歸java堆棧溢出錯誤
- 26. Java堆棧溢出錯誤與遞歸
- 27. 堆棧溢出堆棧溢出
- 28. Java堆棧預遞增器遞歸溢出
- 29. 爲什麼尾部遞歸Collatz猜想會導致Scheme中堆棧溢出?
- 30. 堆棧溢出
我不確定這是否回答了這個問題,但是當我們處理它時,還有其他選擇:切換到優化尾部調用的實現(或僅使用需要TCO的語言);使用[trampolining](http://en.wikipedia.org/wiki/Trampoline_%28computers%29#High_Level_Programming);切換到堆上動態調整大小的堆棧,而不是使用硬件堆棧(也許這就是你的意思是「不使用遞歸」,但我認爲它是「切換到等價的迭代算法」)。 – delnan