1. 程式人生 > >函式呼叫過程理解

函式呼叫過程理解

一、棧

        棧是常見的資料結構,在發生程式呼叫的情況下,作業系統會為被呼叫的子程式開闢一塊棧空間。棧除了先入後出(FILO)外還有以下特性:

每一個程序在使用者態對應一個呼叫棧結構(call stack)
程式中每一個未完成執行的函式對應一個棧幀(stack frame),棧幀中儲存函式區域性變數、傳遞給被調函式的引數等資訊
棧底對應高地址,棧頂對應低地址,棧由記憶體高地址向低地址生長

二、暫存器

        暫存器位於CPU內部,用於存放程式執行中用到的資料和指令,CPU從暫存器中取資料比從記憶體中取快。對於有特定用途的幾個暫存器,簡要介紹如下:

ax(accumulator): 可用於存放函式返回值
EBP(extended base pointer): 存放著一個指標,該指標永遠指向系統棧最上面一個棧幀的棧底
ESP(extended stack poinger): 存放著一個指標,該指標永遠指向系統棧最上面一個棧幀的棧頂
ip(instruction pointer): 指向當前執行指令的下一條指令

                        

        如上圖所示,以 i386 為例,棧底地址為0x0A,儲存在 EBP 暫存器中, 棧頂地址為0x00,儲存在 ESP 暫存器中。如果執行 PUSH 指令,向棧上壓入資料會導致 ESP 的值減小,執行 POP 指令,從棧裡彈出資料則會使得 ESP 增大。

三、函式呼叫過程

函式呼叫步驟為:

引數入棧:將函式的引數從右向左依次壓入系統棧中
返回地址入棧:將當前程式碼區呼叫指令的下一條指令地址壓入棧中,供函式返回時繼續執行
程式碼區跳轉:處理器從當前程式碼區跳轉到被呼叫函式的入口處
棧幀調整:a、儲存當前棧幀狀態值,已備後面恢復本棧幀時使用(EBP入棧)
         b、將當前棧幀切換到新棧幀。(將ESP值裝入EBP,更新棧幀底部)
         c、給新棧幀分配空間。(把ESP減去所需空間的大小,擡高棧頂)

假設程式碼執行的順序為mian函式呼叫fun_A,然後fun_A呼叫fun_B,為了方便理解棧幀變化狀態,在下圖中的棧底位於最下方。

               

函式執行過程為:

在main函式呼叫func_A的時候,首先在自己的棧幀中壓入函式返回地址,然後為func_A建立新棧幀並壓入系統棧在func_A
呼叫func_B的時候,同樣先在自己的棧幀中壓入函式返回地址,然後為func_B建立新棧幀並壓入系統棧
在func_B返回時,func_B的棧幀被彈出系統棧,func_A棧幀中的返回地址被“露”在棧頂,此時處理器按照這個返回地址重新跳到func_A程式碼區中執行
在func_A返回時,func_A的棧幀被彈出系統棧,main函式棧幀中的返回地址被“露”在棧頂,此時處理器按照這個返回地址跳到main函式程式碼區中執行

參考資料:

https://www.zhihu.com/question/22444939/answer/22200552
https://zhuanlan.zhihu.com/p/24291978?refer=hinus
https://zhuanlan.zhihu.com/p/25149493
https://blog.csdn.net/boer521314/article/details/41411593