C語言函數調用詳細過程
函數調用是步驟如下:
-
按照調用約定傳參
-
調用約定是調用方(Caller)和被調方(Callee)之間按相關標准
對函數的某些行為做出是商議,其中包括下面內容:
傳參順序:是從左往右傳還是從右往左
傳參方式:是用寄存器傳還是使用內存傳
平棧方式:是調用方平棧還是被調方平棧
返回值的傳遞方式:是用寄存器傳還是使用內存傳 -
什么是堆桟?
一個程序運行的時候,它的進程的地址空間一般可以分為四塊:
代碼區,數據區,堆,棧,每塊功能如下:區域 功能 代碼區 存放函數被編譯后的二進制可執行代碼 數據區 只讀區:存放常量,例如:常量字符串,const修飾的全局變量等
可讀寫區:存放全局變量和靜態變量堆 除去其他三個區域,剩下的都是堆,不連續 棧 存放函數運行時所需的參數,寄存器環境,返回值,局部變量
以下面代碼為例:
int TestFunction(char szBuff[],int nSize) { for (int iIndex = 0; iIndex < nSize; iIndex++) { szBuff[iIndex] = 'x'; } return 3; } int main() { char szBuff[32] = { "sfjdlskfjl" }; int nRet = TestFunction(szBuff, 32); return 0; }
函數參數參數傳遞:
從上圖中可以看出函數參數入棧 -
-
保存返回地址(緊挨着被調用函數的下一行可執行代碼的內存地址)
從上圖中可以看出函數調用完成后,緊挨着的第一條指令為:
00EB175B add esp,8
所以,參數傳遞完成后就是返回值入棧:
-
程序流程轉移到被調用函數地址處
-
保存調用方棧底
-
切換到當前函數(被調用函數)的棧底
調用方棧底保存完成后,當前的棧頂(ESP記錄的地址)就成為被調用函數的棧底
-
為局部變量分配空間
這里程序為調試版本,所以為局部變量分配的空間比較大,在Release版本中
會根據局部變量實際所需空間來分配大小 -
保存寄存器環境
這里一共保存了3個寄存器,共12字節,在Release版本下,只保存兩個
在Debug版程序中(有/Zi(帶有調試信息)和/Od(禁止優化)編譯命令),除了為
局部變量分配較大的內存空間外,還會將分配的局部變量空間全部置為0xCC:
這種填充方式比較直觀,能夠讓我們在調試時直觀的觀察到是否發生越界等錯誤 -
開始執行函數體代碼
此時當前函數的棧的內存布局如下:
-
恢復寄存器環境
-
釋放分配的局部變量空間
只是將當前的棧底指針(EBP)的值賦值給棧頂指針(ESP)就完成了:
-
恢復調用方棧底
-
平棧或者返回
- 如果是_fastcall,_stdcall調用約定,那么被調用函數平棧后,取出返回地址
函數流程轉移到調用方 - 其他調用約定則是直接取出保存的返回地址,函數流程返回到調用方,又調用方
平棧
- 如果是_fastcall,_stdcall調用約定,那么被調用函數平棧后,取出返回地址