C語言下程序的堆棧調用(詳細,圖示)

以前接觸程序時,只知道程序寫的對,一般都能運行出來,但是卻不知道程序是怎麼一步一步將每一步編譯鏈接起來的,今天我們用匯編來看一下程序到底是怎麼在程序中運行的。

#include <stdio.h>
int Add(int x,int y)
{
	int sum = 0;
	sum = x+y;
	return sum;
}
int main()
{
	int a = 2;
	int b = 3;
	int ret = Add(a,b);
	printf("%d\n",ret);
	return 0;
}
	


我們在寫程序的時候都是先寫main函數,知道main函數是系統函數,直接調用就行。但是,實際上main函數是被三個其他的函數一次調用而來的,而程序在執行的時候會依次給main函數開闢一塊內存,然後在根據我們寫的程序依次以堆棧的形式在這塊內存上存取數據。如此一來,我們隊程序的調用就可以清楚明確的瞭解了。


                                             

在調試的過程中,我們轉到反彙編寫來看看。在調用main函數之後,系統先將ebp(棧底指針)和esp(棧頂指針)壓入到一塊堆棧中(運行時堆棧,又叫棧幀),然後是esp與4cH想減(即把esp指針向上挪4c空間大小),再將ebx,esi,edi分別壓到

堆棧中,經過le啊,mov,mov三條之令之後,main函數的初始空間就被填充成了13h(二進制位19)個cc


                                                        


爲了更加形象的瞭解堆棧的創建和銷燬,我們以圖示的方式來演示一下

                                                  

當吧ebx,esi,edi這三個寄存器壓入堆棧之後,將13h大小的空間初始化cc後,esp指針向上移動到edi處,此時main函數調用纔算結束。程序接着往下執行,這時候到了真正的主程序,執行int a=2, int b=3;看看內存中a=2和b=3是怎麼存儲的

                                               

     有沒有注意到剛剛的ebp指針指向的位置是0018FF3C,現在02和03的位置正是在剛剛ebp指針的上面,一次我們可以想象到一條程序的存儲是先從棧底開始存放,逐層向上

                           


接下來程序往下走進入函數部分


00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,44h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-44h]
0040102C   mov         ecx,11h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]
4:        int sum = 0;
00401038   mov         dword ptr [ebp-4],0
5:        sum = x+y;
0040103F   mov         eax,dword ptr [ebp+8]
00401042   add         eax,dword ptr [ebp+0Ch]
00401045   mov         dword ptr [ebp-4],eax
6:        return sum;
00401048   mov         eax,dword ptr [ebp-4]
7:    }
0040104B   pop         edi
0040104C   pop         esi
0040104D   pop         ebx
0040104E   mov         esp,ebp
00401050   pop         ebp
00401051   ret


我們發現在調用Add函數的時候剛開始和main函數一樣都是先開闢一塊空間,然後就是將ebp和esp壓入棧幀中,同時將11h的空間初始化爲cc,這時候棧底指針和棧頂指針指向發生了變化

                                        

接下來到了好戲時間,先是執行int sum = 0;系統從ebp處往上分配給sum4個字節的內存賦值爲0,接着執行int x+y,仔細看這塊對應的彙編語言,它將ebp+8和ebp+0ch相加起來然後賦給了ebp-4,而我們知道,程序剛剛給sum = 0分配的內存正是ebp-4,而ebp+8h和ebp+0ch就是剛剛a和b形參的位置。這就說明,系統把形參a,b相加起來,把和放在了指向sum的內存處。


程序到這,堆棧的創建就算完成了,接下來就要到把在函數裏執行的結果返回到剛剛在main函數裏創建的指針處,繼續看彙編程序。

0040104B   pop         edi
0040104C   pop         esi
0040104D   pop         ebx
0040104E   mov         esp,ebp
00401050   pop         ebp
00401051   ret

下來就是剛剛創建函數的堆棧的銷燬了,先是pop(彈出堆棧)edi,esi,ebx這三個寄存器,然後將ebp給esp,也就是說把剛剛上面開闢的空間收回,然後經過ret跳回至剛剛在main函數處調用函數那兒(現場保護)。

回到調用處是執行add esp+8,就是說把棧頂指針向下移動8位,也就是剛剛跳過存放形參的位置


  


經過了彙編程序這麼一走,我們看到了程序a+b的值在函數裏運算完,傳給了最初的ebp-0ch,就是剛剛實參b的上面(ret指向的內存),然後再經過printf函數打印出來,然後再執行一遍剛剛銷燬Add函數的步驟,這樣整個函數的堆棧創建和銷燬就算執行完了。


整理一下上面的步驟就是:

1、先給main函數開闢空間

2、將寄存器和實參分別壓到堆棧中

3、開闢一片形參空間,然後實參傳給形參

4、給Add函數開闢一片空間,將寄存器和函數裏的程序壓入到這片空間中

5、把剛剛的形參進行運算,將計算結果放在函數的空間中

6、把結果傳遞給主函數,並且打印出來

7、Add函數的銷燬

8、主函數的銷燬

發佈了43 篇原創文章 · 獲贊 47 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章