一,程序空間與資料分布
程式,是經源碼編譯後的可編譯檔案,可執行檔案可以多次被執行,比如我們可以多次打開某個應用。
程序,是程式加載到記憶體後開始執行、至執行結束,這樣的一段時間概念,多次打開的應用,每打開一次都是一個程序,當我們關閉該應用時,該程序也就結束了。
程式是靜态概念,而程序是動态/時間概念。
接下來,通過一個程式執行個體來說明:
include<stdio.h>
int a; //a為全局變量且未初始化,位于bss區
static int b; //b為靜态變量且未初始化,位于bss區
int c=2; //c為全局變量、已初始化為2且可讀可寫,位于rw區
static int d=4; //d為靜态變量、已初始化為4且可讀可寫,位于rw區
int main()
{
int m=5; //m為局部變量,位于stack區
static int n=6;
//n為靜态變量、已初始化為6,且可讀可寫,位于rw區
char *p="china"; //p為局部變量,位于stack區
int array[10]={0,1,2,3,4,5,6,7,8,9};
//array為數組,位于stack區
int *q=(int *)malloc(100);
//malloc動态申請的空間位于heap區
int fun();
}
int fun()
{
int m; //m為局部變量,位于stack區
static int n; //n為靜态變量、且未初始化,位于bss區
return 0;l
}
二,函數壓棧與出棧
1,普通函數
以下面的代碼為例,我來展示程式運作時程序空間的資料分布:
#include<stdio.h>
int m=10;
int fun(int a,int b);
int main()
{
int i=4;
int j=5;
m=fun(i,j);
return 0;
}
int fun(int a,int b)
{
int c=0;
c=a+b;
return c;
}
cpu中有三個寄存器,分别為:
- eip——指向代碼區将要執行的指令,有兩種移動方式,分别是“順序”和“跳轉”;
- ebp——始終指向棧底;
- esp——始終指向棧頂;
接下來,我将逐漸以圖文結合的方式來分析程式的運作過程:
1,初始狀态時,程式尚未運作,棧空間為空、eip指向代碼區的第一條指令——int main(),而ebp和esp則指向“核心設定的初始狀态。如圖所示:
2,程式開始執行main函數的第一條指令時,main函數在棧區建立“main函數棧空間。ebp的位址壓棧——被儲存到“main函數棧空間”以便結束後傳回到初始狀态。esp會移動到“main函數棧空間”的棧頂。而此時的ebp也會移動到“main函數棧空間”的棧底。eip指向下一條指令(int =4)。如圖所示:
3,main函數棧空間中會開辟一個空間 i,指派為4。ebp不移動。esp移動到棧頂。eip指向下一條指令(int j=5)。如圖所示:
4,main函數棧空間中開辟一個空間 j ,指派為5。ebp不移動。esp移動到棧頂。eip指向下一條指令( m=fun() )。如圖所示:
5,此過程要傳遞兩個參數。這兩個參數均儲存在“main函數棧空間”中。但卻要被“fun函數棧空間”所使用。main函數棧空間中為兩個參數a和b開辟空間。傳參順序與代碼書寫順序正好相反。j 的值會傳給a,而 i 的值會傳給b。ebp不移動。esp移動到棧頂。如圖所示:
6,此時,main函數棧空間會開辟一個空間來儲存fun函數的傳回值。然後,main函數棧空間再次開辟一個空間儲存fun函數的傳回位址,以便fun函數傳回時能找到原來的位置。ebp不移動。esp移動到棧頂。如圖所示:
7,eip跳轉到fun函數的第一條指令( int fun() )。main函數棧空間開辟一個空間用來儲存此時ebp的指向位置,即main函數棧空間的棧底位址。這樣的話,當fun函數執行完、傳回後,ebp能重新回到main函數棧空間的棧底。如圖所示:
8,此時,開始建構fun函數棧空間。ebp移動到“fun函數棧空間”的棧底。實際上,由于此時棧内并沒有資料,棧底與棧頂處于同一位址,ebp與esp指向重合。eip指向下一條指令(int c=0)。執行這一語句,fun函數棧空間會開辟一個空間C,值為0。ebp不移動。esp移動到棧頂。eip指向下一條指令( c=a+b )。如圖所示:
9,此時,main函數棧空間中,a和b的值相加然後賦給C。C值變為9。ebp和esp均不移動。eip指向下一條指令( return 0 )。如圖所示:
10,fun函數運作結束,其傳回值會傳到main函數棧空間的“儲存fun函數傳回值”的空間中,然後指派給m。如圖所示:
11,fun函數執行完畢。此時有三個任務:
ebp和esp重新指向main函數棧空間的棧底和棧頂,eip隻想main函數中将要執行的指令。 fun函數傳回到原來位址,繼續執行main函數指令。 fun函數棧空間被回收釋放
如圖所示:
12,main函數傳回後,程式執行結束。ebp和esp回到初始狀态——核心設定的初始指向位址。main函數棧空間被清空。靜态資料區清空。代碼區也被清。程式執行結束!