函式呼叫與遞迴

2021-06-21 09:22:50 字數 1992 閱讀 7411

「遞迴,就是遞迴的呼叫自己,直到滿足結束條件並返回。」這是大部分材料對遞迴的定義,從程式上看也符合一般的觀感。但是,不了解函式的呼叫過程,對這句話的理解還是會模糊不清的。函式是怎麼呼叫自己,又是如何返回的?下面是常用來說明遞迴呼叫的例子:

/******

**遞迴求n的階乘n!**/

int factorial( int n )

遞迴是程式語言的精髓概念之一,徹底的掌握顯然不會那麼容易。「世界是複雜的,解釋世界的理論需簡單,並帶有豐富的變化,方能可行。」

第一句的遞迴定義中就含有「呼叫」的概念。程式從main函式開始一步一步的呼叫函式達到程式設計的目的。顯然「函式呼叫」也是程式語言的精髓概念之一了,而且是遞迴概念的前導。

呼叫過程涉及到計算機cpu內部的一些概念,最主要的有棧(stack)、棧幀(fram stack)、返回值(一般通過ax暫存器)、基址暫存器(bp)、棧暫存器(sp)、指令指標暫存器(ip)等。言簡意賅的說,每個函式在執行過程中,在棧上都以棧幀的形式儲存臨時變數,也就是中間結果。如果它還呼叫其他函式,需要傳遞的引數也是通過棧幀來傳遞的(不同體系結構可能不一樣)。每個棧幀有開始和結束,bp暫存器儲存當前棧幀的起始位置(保持不變),sp指示棧頂的位置,隨著函式執行會有出棧、入棧操作,sp的值會隨之發生變化。ip始終指向下乙個執行的彙編**的虛擬位址。呼叫函式(caller)在呼叫被調函式(callee)之前,首先會把引數壓棧,然後執行call 指令,cpu就會把當前過程的下一條指令的位址壓棧(這一步是cpu自動執行的,彙編**看不出棧的變化),以便呼叫返回時繼續執行;還會儲存標誌位暫存器flag和段暫存器cs(視不同呼叫型別而定)。callee執行,首先會把caller棧幀的bp壓棧(返回時需要恢復),然後將當前的sp賦值給bp,表明這時的bp是callee的棧幀起始位址;然後取出caller傳進來的引數(在caller棧幀中,通過bp暫存器為參考點獲取引數,(bp+8),(bp+12)...)。這裡可能會有點困惑的是(bp+0),(bp+4)的內容是什麼?上面說到進入callee後首先要做的就push %bp,此處的bp暫存器儲存的是上一棧幀的起始位置。而進入callee必須儲存上一過程的返回位址。這樣就清楚了(bp+0)的值是上乙個過程的棧幀值,(bp+4)是callee返回後caller執行的下乙個指令位址

。通過這樣的函式呼叫規約,程式執行時的每次呼叫就能準確無誤。如果有返回值,一般以ax暫存器傳遞返回值,那麼在返回caller後,需儲存ax暫存器的值。

前面丟擲函式呼叫的磚,目的還是為了引出遞迴的玉。上述分析能否得出,被調函式不能是呼叫函式自身,也就是caller和callee不能是同乙個函式?答案是,否!呼叫過程是通過棧來維護的,棧處於caller與callee的中立位置。邏輯上caller和callee是否同一函式,對棧來說並無區別,都必須將引數入棧,caller中下一條指令的位址入棧,call ,在caller中取出引數,執行判斷或(和)執行,然後返回或者繼續呼叫下乙個過程。在求階乘的例子中,函式factorial首先判斷n是否為0,如果是則返回1,否則返回n*factorial(n-1),這個表達中有個子表示式就是呼叫factorial自身,然後與n相乘。那麼,必須首先求出子表達的值,也就是呼叫factorial計算引數為n-1時的返回值。在這裡可以**,假設返回值是x,返回後首先必須計算n*x,並把它作為最終的factorial返回值。要得到x必須判斷n-1是否為0,不為0還要繼續呼叫,並且將呼叫的返回值與n-1相乘,一直遞迴這個過程,直到n減到0,最終返回。然後逐步執行上乙個caller的相乘操作,並返回到上上乙個caller……

函式呼叫過程中,並沒有規定這個被調函式不能是自身,而遞迴恰恰是就是函式呼叫中的特例,caller和callee是同乙個函式。因此,遞迴函式體必須包含呼叫截止的判斷,否則無限制的遞迴呼叫必然導致棧溢位而使程式崩潰。形式上看,遞迴函式包含兩個部分,乙個是結束判斷(terminating cases),乙個是遞迴呼叫(recursive cases)。factorial函式的recursive case只有乙個,屬於線性遞迴。當有多個recursive case時就是樹形遞迴(tree recursion)。但是遞迴過程的實質是一樣的,其大體過程也相似。

函式遞迴呼叫

我們學習了函式的巢狀呼叫,可以在函式中呼叫函式。那麼,如果在乙個函式中,呼叫自己這個函式,那麼,這個執行過程稱為 函式遞迴呼叫。這個函式也稱為 遞迴函式。程式測試例子 程式執行結果如下 在這個測試例子中,我們定義了func函式,在func函式中又呼叫了func函式自己 所以,這個過程稱為 遞迴呼叫。...

遞迴與函式呼叫詳細解讀

草稿箱存不下你了 當乙個函式執行期間呼叫另乙個函式時,在執行被調函式之前,系統需要完成三件事 從主調函式返回被調函式之前,系統要完成三件事 當有多個函式相互呼叫時,按照後調先返回的原則,上述函式之間資訊傳遞和控制轉移必須借助棧來實現,即系統將整個程式執行時所需的資料空間安排在乙個棧中,每當呼叫乙個函...

函式的定義與呼叫 遞迴

3.6函式檔案的定義與呼叫 函式檔案的基本結構 函式呼叫 匿名函式 1.函式檔案的基本結構 function 輸出形參表 函式名 輸入形參表 function定義乙個函式 注釋說明部分 函式體語句 當有多個形參時,形參之間用逗號分隔,組成形參表。當輸出形參多於乙個時,應該用方括號括起來,構成乙個輸出...