天天看點

C++程式棧與尾遞歸優化

如果一個函數中所有遞歸形式的調用都出現在函數的末尾,我們稱這個遞歸函數是尾遞歸的。當遞歸調用是整個函數體中最後執行的語句且它的傳回值不屬于表達式的一部分時,這個遞歸調用就是尾遞歸。尾遞歸函數的特點是在回歸過程中不用做任何操作,這個特性很重要,因為大多數現代的編譯器會利用這種特點自動生成優化的代碼。  -----取自百度百科

隻要了解了函數調用棧,那麼尾遞歸就很好了解了。如果你還不了解什麼叫函數調用棧

那麼請看這篇博文。

接下來看一個典型的遞歸函數:求階乘

C++程式棧與尾遞歸優化

rfact的幀如下圖所示:(過程就是指函數)

C++程式棧與尾遞歸優化

這個遞歸函數rfact(n)由于語句n*rfact(n-1)的存在,在調用rfact(n-1)之後還要用到rfact(n)裡的一個變量n,是以就必須在rfact(n)裡儲存n,是以就必須為rfact(n)儲存一個幀,同理,必須為rfact(n-1),rfact(n-2)rfact(n-3)........rfact(1)儲存一個幀。由此可以看出+++++++》如果n過大的話,那麼儲存的幀就會過多,而我們知道一個程式獲得的程式棧大小是有限的,是以在運作過程中就很有可能會爆棧。。。爆棧。。。。。

讓我們好好想一下:如果一個函數(調用者)在調用另一個函數(被調用者)時,調用者不需要儲存任何變量,那麼我們是不是就可以把被調用者的幀建立在調用者的幀上,即通過覆寫調用者的幀而不是開拓新的空間,來達到節省棧空間的目的呢,這樣,不就大大降低了爆棧的幾率麼

說幹就幹,讓我們改寫上面的遞歸函數吧:

C++程式棧與尾遞歸優化

仔細觀察上面的函數,我們會發現rfact(n)函數裡并不需要儲存什麼變量,是以就沒必要儲存rfact(n)的幀,這裡的關鍵就在于把結果當做一個參數傳給rfact(n-1)是以我們可以通過覆寫調用者的幀來建立被調用者的幀,進而達到節約棧空間的目的。哈!這不就是所謂的尾遞歸嗎(請回到這篇文章的開頭好好體會一下,體會那種豁然開朗的感覺 )

也許你會問:我的上一篇博文裡不是說了傳遞給被調用者的參數是存儲在調用者的幀裡麼?

我的上篇博文裡不是有esp ,ebp兩個東東嗎,你難道沒有發現程式并沒有配置設定空間來儲存esp,ebp兩個“指針麼”。Esp,ebp就是寄存器之一,以前的cpu一共有八個寄存器,顧名思義,寄存器就是臨時存放變量的東西,如果參數不是很多的情況下就可以将函數參數儲存在寄存器裡。現在的cpu寄存器翻倍了,有16個通用寄存器了,其中6個可以用來存放參數,是以你懂的,至于超過六個嘛,很抱歉,樓主對編譯原理還不是很清楚,不知道寫編譯器的人會怎麼處理(我估計還是能實作尾遞歸優化)我再給說一下:我們的程式棧是存放在存儲區裡(也就是記憶體)寄存器和記憶體是不同的兩種緩沖區。

繼續閱讀