為什麼函式呼叫要用棧實現?

2021-08-20 18:25:40 字數 1512 閱讀 8578

「呼叫棧」(call stack)既可以指具體實現,也可以指一種抽象概念——由「棧幀」(stack frame)或者叫「活動記錄」(activation record)構成的棧。

函式呼叫的區域性狀態之所以用棧來記錄是因為這些資料的存活時間滿足「後入先出」(lifo)順序,而棧的基本操作正好就是支援這種順序的訪問。

舉例說,假如有下面程式:

int

main

()voida()

voidb()

voidc()

那麼整個程式的函式活動時間可以表示為:

main()  a()  b()  c()

- main()

|+> - a()

. |

. +> - b()

. . |

. . +> - c()

. . . |

. . + <- return from c()

. . |

. + <- return from b()

. |

+ <- return from a()

|- return from main()

可以看到,函式的呼叫有完美的巢狀關係——呼叫者的生命期總是長於被呼叫者的生命期,並且後者在前者的之內。

這樣,被呼叫者的區域性資訊所佔空間的分配總是後於呼叫者的(後入),而其釋放則總是先於呼叫者的(先出),所以正好可以滿足棧的lifo順序,選用棧這種資料結構來實現呼叫棧是一種很自然的選擇。

這個道理在mit的6.001 sicp課的第一節裡就有非常好的講解了:

1b: procedures and processes; substitution model

有興趣的同學請把sicp整門課都看看,會有許多收穫。

(擴充套件:順著sicp的線索學下去可以看到函式呼叫的順序雖然跟lifo順序一致,但是呼叫者的棧幀並不一定要保留,在特殊情況下可以不保留呼叫者棧幀——尾呼叫(tail call)的情況。

關鍵點在於呼叫者是否在乙個函式呼叫之後還有待執行的計算。如果沒有了(這個函式呼叫是尾呼叫),那呼叫者的區域性狀態就沒有必要保留。)

在滿足lifo順序的情況下,實際對映到體系結構上,倒不一定要真的在記憶體裡實現完整的呼叫棧。

例如說像sparc有暫存器視窗,區域性資訊大都可以維持在暫存器裡;當暫存器不夠用的時候再由cpu與os協作把部分暫存器的值暫時存到記憶體裡——這部分看起來就會跟傳統的用記憶體實現的呼叫棧類似。

然而不是所有程式語言的「函式」呼叫中區域性資訊的儲存都是滿足lifo順序的。

為什麼函式呼叫要用棧實現?

呼叫棧 call stack 既可以指具體實現,也可以指一種抽象概念 由 棧幀 stack frame 或者叫 活動記錄 activation record 構成的棧。函式呼叫的區域性狀態之所以用棧來記錄是因為這些資料的存活時間滿足 先出 lifo 順序,而棧的基本操作正好就是支援這種順序的訪問。舉...

為什麼要用 enable shared from

樓主 hma if you think you can,you can.panrainbow 憂鬱淡藍 於 tue nov 9 11 48 38 2010 提到 引入enable shared from this的原因是可以實現返回值為指向該類本身的 shared ptr,為什麼以this為拷貝構造...

為什麼要用補碼

在探求為何機器要使用補碼之前,讓我們先了解原碼,反碼和補碼的概念.對於乙個數,計算機要使用一定的編碼方式進行儲存.原碼,反碼,補碼是機器儲存乙個具體數字的編碼方式.原碼就是符號位加上真值的絕對值,即用第一位表示符號,其餘位表示值.比如如果是8位二進位制 1 原 0000 0001 1 原 1000 ...