程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C開發基礎--函數調用棧,開發基礎--函數

C開發基礎--函數調用棧,開發基礎--函數

編輯:C++入門知識

C開發基礎--函數調用棧,開發基礎--函數


發現有一些問題幾乎是所有的新人都會遇到,而且也常因為缺乏一些基本的知識而無從下手。函數調用棧的內容就是其中之一。於是花點時間把以前寫的內容整理出來。

程序在運行期間,內存中有一塊區域,用來實現程序的函數調用機制。這塊區域是一塊LIFO的數據結構區域,我們可以叫函數棧(調用棧)。每個未退出的函數都會在函數棧中擁有一塊數據區,我們叫函數的棧幀。函數的調用棧幀中,保存了相應的函數的一些重要信息:函數中使用的局部變量,函數的參數,另外還有一些維護函數棧所需要的數據,比如EBP指針,函數的返回地址。如下圖。我們假設程序當前執行的函數是Z函數,那麼在函數調用棧中就會存在類似像這樣的結構(EBP所指向的其實是“父函數”的調用棧幀,如何做到的後面會解釋):

  • int increase(int a) {
  •  
  •     int temp = 4;
  •  
  •     return a + 3;
  • }
  •  
  • int main(int argc, char* const argv[])
  • {
  •     int sum = increase(3);
  •  
  •     return 0;
  • }
  • 在main函數中調用 increase 函數。用VS單步斷點打開匯編模式,可以看到如下的代碼

    1.     int sum = increase(3);
    2. 00D2561E  push        3  
    3. 00D25620  call        increase (0D2142Eh)  
    4. 00D25625  add         esp,4  
    5. 00D25628  mov         dword ptr [sum],eax  

    對照前面的說明,我們可以看到,調用函數前有 push 指令先把函數參數壓棧。之後才真正的call increase 。然後我們進入 increase 函數再看看函數體是什麼樣的。

    1. int increase(int a) {
    2. 000455C0  push        ebp  
    3. 000455C1  mov         ebp,esp  
    4. 000455C3  sub         esp,0CCh  
    5. 000455C9  push        ebx  
    6. 000455CA  push        esi  
    7. 000455CB  push        edi  
    8. 000455CC  lea         edi,[ebp-0CCh]  
    9. 000455D2  mov         ecx,33h  
    10. 000455D7  mov         eax,0CCCCCCCCh  
    11. 000455DC  rep stos    dword ptr es:[edi]  
    12.  
    13.     int temp = 4;
    14. 000455DE  mov         dword ptr [temp],4  
    15.  
    16.     return a + temp;
    17. 000455E5  mov         eax,dword ptr [a]  
    18. 000455E8  add         eax,dword ptr [temp]  
    19. }
    20. 000455EB  pop         edi  
    21. 000455EC  pop         esi  
    22. 000455ED  pop         ebx  
    23. 000455EE  mov         esp,ebp  
    24. 000455F0  pop         ebp  
    25. 000455F1  ret  

    進入函數前,做的動作主要是保存各寄存器,注意“sub esp,0xcch”就是移動ESP,空出局部變量的“位置”,為什麼只有一個局部變量,卻生成了這麼大塊區域呢?

    Stackoverflow上有解釋:

       

    This extra space is generated by the /Zi compile option. Which enables Edit + Continue. The extra space is available for local variables that you might add when you edit code while debugging.

    You are also seeing the effect of /RTC, it initializes all local variables to 0xcccccccc so that it is easier to diagnose problems due to forgetting to initialize variables. Of course none of this code is generated in the default Release configuration settings.

    從這段簡單的代碼中,我們可以知道函數調用大概是什麼回事了。通過上面的內容,我們仔細體會下ESP和EBP兩個寄存器的變化,也就下面向個指令

       

    013D55C0  push        ebp      // 構建新的調用幀
    013D55C1  mov         ebp, esp

    013D55EE  mov         esp, ebp // 恢復到原來的調用幀
    013D55F0  pop         ebp

    再加上參數,返回地址,局部變量的入棧出棧,通過這樣一種統一的、並不復雜代碼生成模式和數據結構,可以應對任意復雜的函數調用情況,極其靈活。我一直覺得這是計算機科學中非常漂亮的一個創造,也是以簡馭繁的一個經曲例子。

    1. 上一頁:
    2. 下一頁:
    Copyright © 程式師世界 All Rights Reserved