天天看點

C語言之程序空間---程序空間、資料分布、函數壓/出棧

一,程序空間與資料分布

程式,是經源碼編譯後的可編譯檔案,可執行檔案可以多次被執行,比如我們可以多次打開某個應用。

程序,是程式加載到記憶體後開始執行、至執行結束,這樣的一段時間概念,多次打開的應用,每打開一次都是一個程序,當我們關閉該應用時,該程序也就結束了。

程式是靜态概念,而程序是動态/時間概念。

C語言之程式空間---程式空間、資料分布、函數壓/出棧

接下來,通過一個程式執行個體來說明:

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則指向“核心設定的初始狀态。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

2,程式開始執行main函數的第一條指令時,main函數在棧區建立“main函數棧空間。ebp的位址壓棧——被儲存到“main函數棧空間”以便結束後傳回到初始狀态。esp會移動到“main函數棧空間”的棧頂。而此時的ebp也會移動到“main函數棧空間”的棧底。eip指向下一條指令(int =4)。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

3,main函數棧空間中會開辟一個空間 i,指派為4。ebp不移動。esp移動到棧頂。eip指向下一條指令(int j=5)。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

4,main函數棧空間中開辟一個空間 j ,指派為5。ebp不移動。esp移動到棧頂。eip指向下一條指令( m=fun() )。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

5,此過程要傳遞兩個參數。這兩個參數均儲存在“main函數棧空間”中。但卻要被“fun函數棧空間”所使用。main函數棧空間中為兩個參數a和b開辟空間。傳參順序與代碼書寫順序正好相反。j 的值會傳給a,而 i 的值會傳給b。ebp不移動。esp移動到棧頂。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

6,此時,main函數棧空間會開辟一個空間來儲存fun函數的傳回值。然後,main函數棧空間再次開辟一個空間儲存fun函數的傳回位址,以便fun函數傳回時能找到原來的位置。ebp不移動。esp移動到棧頂。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

7,eip跳轉到fun函數的第一條指令( int fun() )。main函數棧空間開辟一個空間用來儲存此時ebp的指向位置,即main函數棧空間的棧底位址。這樣的話,當fun函數執行完、傳回後,ebp能重新回到main函數棧空間的棧底。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

8,此時,開始建構fun函數棧空間。ebp移動到“fun函數棧空間”的棧底。實際上,由于此時棧内并沒有資料,棧底與棧頂處于同一位址,ebp與esp指向重合。eip指向下一條指令(int c=0)。執行這一語句,fun函數棧空間會開辟一個空間C,值為0。ebp不移動。esp移動到棧頂。eip指向下一條指令( c=a+b )。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

9,此時,main函數棧空間中,a和b的值相加然後賦給C。C值變為9。ebp和esp均不移動。eip指向下一條指令( return 0 )。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

10,fun函數運作結束,其傳回值會傳到main函數棧空間的“儲存fun函數傳回值”的空間中,然後指派給m。如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

11,fun函數執行完畢。此時有三個任務:

ebp和esp重新指向main函數棧空間的棧底和棧頂,eip隻想main函數中将要執行的指令。 fun函數傳回到原來位址,繼續執行main函數指令。 fun函數棧空間被回收釋放

如圖所示:

C語言之程式空間---程式空間、資料分布、函數壓/出棧

12,main函數傳回後,程式執行結束。ebp和esp回到初始狀态——核心設定的初始指向位址。main函數棧空間被清空。靜态資料區清空。代碼區也被清。程式執行結束!

C語言之程式空間---程式空間、資料分布、函數壓/出棧

繼續閱讀