堆疊框
函式被呼叫時,它需要自己的一張小書桌來工作——一個地方放它的引數、區域變數,以及一張「該回到哪裡」的便條。堆疊框(stack frame,又稱活動記錄 activation record)正是那張書桌:呼叫堆疊中屬於某一個活躍呼叫的那一片。每個呼叫拿到一個全新的框;呼叫結束時,框就被清掉。
一個框通常保存:函式的區域(自動)變數、引數的拷貝、回到呼叫者的返回位址,以及常見的呼叫者框指標暫存器的存檔(x86-64 上是 rbp),好讓佈局能乾淨地展開。框由函式開頭一段簡短的序言(prologue,調整堆疊指標以挖出空間)建立,並由結尾的收尾(epilogue,釋放那片空間並跳到返回位址)拆除。區域變數不過是這個框內具名的偏移——例如編譯器可能把 int x 放在 rbp - 4——這就是為什麼區域變數在進入時誕生、在返回時消亡。
框正是區域變數之所以「自動」的原因:它們在框被推入時自動建立、在框被彈出時自動銷毀,幾乎不費成本。它也是一個經典臭蟲的來源——回傳一個區域變數的位址,等於交回一個指向「已不存在的框」的指標,一個指向已回收堆疊記憶體的懸置指標。框做完事就走了;存活下來的那個位址現在指名的是垃圾。
int *bad(void) { int local = 5; return &local; } ——bad 回傳 local 的位址,但 local 住在 bad 的框裡,而那個框在 bad 一返回的瞬間就沒了。呼叫者拿到一個懸置指標;使用它是未定義行為。
回傳 &local 是經典臭蟲——框已消失,位址已過期。
一個框的區域變數只在那一次呼叫期間存在;同一函式巢狀呼叫(遞迴)時,每個呼叫各有自己獨立的框與自己的一份區域變數。絕不要回傳或藏起指向區域變數的指標——函式一返回,它的框就被重用,指標便懸置。