C語言函數調用完整過程


C語言函數調用詳細過程

函數調用是步驟如下:

  1. 按照調用約定傳參

    • 調用約定是調用方(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;
        }
    

    函數參數參數傳遞:
    傳遞參數.png
    從上圖中可以看出函數參數入棧

  2. 保存返回地址(緊挨着被調用函數的下一行可執行代碼的內存地址)
    從上圖中可以看出函數調用完成后,緊挨着的第一條指令為:
    00EB175B add esp,8
    所以,參數傳遞完成后就是返回值入棧:
    返回地址入棧.png

  3. 程序流程轉移到被調用函數地址處

  4. 保存調用方棧底
    保存調用方棧底.png

  5. 切換到當前函數(被調用函數)的棧底
    調用方棧底保存完成后,當前的棧頂(ESP記錄的地址)就成為被調用函數的棧底
    切換到被調用函數的棧底.png

  6. 為局部變量分配空間
    為局部變量分配空間.png
    這里程序為調試版本,所以為局部變量分配的空間比較大,在Release版本中
    會根據局部變量實際所需空間來分配大小

  7. 保存寄存器環境
    這里一共保存了3個寄存器,共12字節,在Release版本下,只保存兩個
    保存寄存器環境.png
    在Debug版程序中(有/Zi(帶有調試信息)和/Od(禁止優化)編譯命令),除了為
    局部變量分配較大的內存空間外,還會將分配的局部變量空間全部置為0xCC:
    局部變量空間賦值CC.png
    這種填充方式比較直觀,能夠讓我們在調試時直觀的觀察到是否發生越界等錯誤

  8. 開始執行函數體代碼
    此時當前函數的棧的內存布局如下:
    當前函數堆桟布局.jpeg

  9. 恢復寄存器環境
    恢復寄存器環境.png

  10. 釋放分配的局部變量空間
    只是將當前的棧底指針(EBP)的值賦值給棧頂指針(ESP)就完成了:
    釋放局部變量空間.png

  11. 恢復調用方棧底
    恢復調用方棧底.png

  12. 平棧或者返回

    • 如果是_fastcall,_stdcall調用約定,那么被調用函數平棧后,取出返回地址
      函數流程轉移到調用方
    • 其他調用約定則是直接取出保存的返回地址,函數流程返回到調用方,又調用方
      平棧


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM