C語言函數棧幀的創建與銷毀原理圖解
什麼是函數棧幀
我們在寫C語言代碼的時候,經常會把一個獨立的功能抽象為函數,所以C程序是以函數為基本單位的。
那函數是如何調用的?函數的返回值又是如何待會的?函數參數是如何傳遞的?這些問題都和函數棧幀有關系。
函數棧幀(stack frame)就是函數調用過程中在程序的調用棧(call stack)所開辟的空間,這些空間是用來存放:
- 函數參數和函數返回值
- 臨時變量(包括函數的非靜態的局部變量以及編譯器自動生產的其他臨時變量)
- 保存上下文信息(包括在函數調用前後需要保持不變的寄存器)。
什麼是棧?
棧(stack)是現代計算機程序裡最為重要的概念之一,幾乎每一個程序都使用瞭棧,沒有棧就沒有函 數,沒有局部變量,也就沒有我們如今看到的所有的計算機語言。
與函數棧幀有關的匯編語句
eax:通用寄存器,保留臨時數據,常用於返回值
ebx:通用寄存器,保留臨時數據
ebp:棧底寄存器
esp:棧頂寄存器
eip:指令寄存器,保存當前指令的下一條指令的地址
mov:數據轉移指令
push:數據入棧,同時esp棧頂寄存器也要發生改變
pop:數據彈出至指定位置,同時esp棧頂寄存器也要發生改變
sub:減法命令
add:加法命令
call:函數調用,1. 壓入返回地址 2. 轉入目標函數
jump:通過修改eip,轉入目標函數,進行調用
ret:恢復返回地址,壓入eip,類似pop eip命令(返回子程序)
函數如何創建棧幀並銷毀
當程序進入main函數時,要給main函數在棧區創建空間,esp(棧頂)和ebp(棧底)對main函數進行維護
當程序執行時,我們在調試窗口對堆棧段進行調用,我們可以看到main函數是被__tmainSRTStartup函數所調用,說明main函數是它的內部函數,而__tmainSRTStartup又是被mainCRStartup這個函數調用
梳理一下上面的思路
這些函數在堆棧當中的存儲
main函數棧幀開辟
接下來重新調試,我們轉到反匯編
調用main函數之前,esp和ebp對調用main函數的函數進行維護 ,當棧頂發生改變時,esp會指向新的棧頂
003118B0 push ebp
003118B1 mov ebp,esp
先把ebp入棧,然後把ebp移動到esp的位置
003118B3 sub esp,0E4h
003118B9 push ebx
003118BA push esi
003118BB push edi
之後又把esp往上移動,移動完把ebx,esi,edi壓棧,esp和ebp現在指的這塊空間是為main函數預先開辟好的
把edi-0EFH也就是main函數開始的這裡的地址,放到edi裡面去,然後從這個地址開始賦值39次的雙字節數據,賦值為CCCC
到這裡main函數棧幀開辟完成
接下來就是在main函數的空間裡,創建三個變量,並給賦值
調用Add函數
對函數進行傳參,創建倆個臨時變量,然後壓棧進去
接下來進入call開始調用函數call此時的地址是00C2144B
此時按下F11,我們發現call指令的下一條地址被壓到瞭棧區
把call的下一個地址壓棧,ps:後面會用到這條指令,可先放在這不管
接下來進入Add函數,跟前面main函數一樣,先開辟空間,然後賦值為CCCCCC,再為變量在函數裡創建空間並賦值
接下來執行加法運算,由於剛才已經創建好瞭零時變量,所以把他倆進行相加,加完之後把結果傳過來就行,傳過來之後把這個值放在eax裡面去
返回主函數
按順序出棧,之後把ebp賦值給esp
之後pop,ebp把ebp進行出棧,ebp便回到main函數這裡,ebp此時回到這裡,esp也自然而然的往下指一個,ret指令是返回,然後esp來到瞭call指令的下一條指令
把棧頂指針彈出去,esp自然向下指一條
之後給esp加8即釋放這倆個臨時變量
之後把eax放到ebp-20h,eax是存放剛才加法和的地方
到此這篇關於C語言函數棧幀的創建與銷毀原理圖解的文章就介紹到這瞭,更多相關C語言函數棧幀內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!