C++的函數調用過程, 需要調用初始化和善後處理的環節。函數調用的整個過程就是棧空間操作的過程。函數調用時,C++首先:
(1)建立被調函數的棧空間。
(2)保護調用函數的運行狀態和返回地址。
(3)傳遞參數。
(4)將控制轉交被調函數。
例如,下面的代碼在主函數中調用一個函數,該函數又調用了另一個函數,它得到圖5-3中所示的內存佈局:
void funcA(int,int);
void funcB(int);
void main()
{
int a=6,b=12;
funcA(a,b);
}
void funcA(int aa,int bb)
{
int n=5;
//...
funcB(n);
}
void funcB(int s)
{
int x;
//...
}
該函數運行的棧區分佈圖是示意性的。函數的棧操作原理是一致的,但具體的實現因編譯器不同而不同。介紹函數棧的操作具體過程之目的是要讓讀者對函數運行的機制有一個感性的認識。
在圖5-3中,主函數main()返回的地址應該是在操作系統環境中。 其參數是操作系統傳遞過來的,在8.9節中將對其進行介紹。主函數定義了兩個局部變量a和b,a和b的次序並不一定如圖所示, 這些實現的細節都是與C++標準無關的。我們觀察棧內存時,可以忽略返回地址和調用函數運行狀態。
當主函數調用funcA()時,funcA()着手保護調用函數的地址等數據,分配兩個形參的空間,將主函數的實參傳遞過來。 所以funcA()中的形參aa和bb分別爲6和12。
funcA()的棧區和main()的棧區是互相獨立的。在funcA()中不能訪問main()中的局部變量a和b。通過參數傳遞, 使得funcA()中的形參賦有main()函數中的實參值。funcA()可以修改其變量aa和bb, 但始終不會影響main()中的a和b。這便是C++函數的參數傳值特性。
圖5—3 函數調用機制中的棧結構
在函數funcA()中,定義了一個局部變量n, 並以該變量作實參來調用函數funcB()。同樣,funcB()建立了它自己的棧空間,保護funcA()的返回地址,獲取參數值,隨後運行它自己的函數體語句。
棧是有限的資源,每次調用一個函數,
剩餘的棧空間會逐漸減少。可以看出,如果一層層地調用下去,或者,過多的定義局部變量特別是數組 (將在第7章介紹),最後可能導致棧空間枯竭而引起程序運行出錯。如果程序確實要佔用相當大的棧空間,可以在連接前通過設置棧空間大小來改善 (見實驗指導書)。
函數在返回時,將把返回值保護在臨時變量空間中(如果有返回值的話),然後恢復調用函數的運行狀態,釋放棧空間,使其屬於調用函數棧空間的一部分,最後根據返回地址,回到調用函數。
圖5-4是funcB()返回到funcA(),funcA()又回到main()時的棧內容。 可以看出,返回到主函數後, funcA()和uncB()的局部變量和形參都消失,但對應內存中的內容還是存在着,這就是上節中,程序chil.cpp得到該運行結果的原因。