C/C++中堆棧相關

學習過C語言的同學應該都知道內存是什麼,以及怎麼用。但是程序代碼編譯目標文件和可執行文件是什麼格式?程序在內存裏是怎麼組織的呢?分爲哪幾個部分?堆棧有哪些具體功能?當函數調用時發生了什麼?等等問題。並不一定每個人都很清楚。下面就寫點我以前看書時的日記和理解。

一、             首先來講一下目標文件和可執行文件的格式

不同的系統的可執行文件有不同的格式。在SVr4實現中都採用了ELF(Extensible and Linker Format,可執行文件夾和鏈接格式)的格式,在其他系統中,可執行文件的格式是COFF(Common Object-File Format,普通目標文件格式,在BSD UNIX中也有自己自己的格式。可以通過命令man a.out 查看。這裏就不詳細介紹了。

但是所有這些不同格式都有一個共同的概念,那就是段(segments)。它是二進制文件中簡單的區域,裏面保存了和某些特定類型相關的所有信息,可以通過size a.out來查看:

[root@localhost ~]# size a.out

   text    data     bss     dec     hex filename

   1197     512       8    1717     6b5 a.out

可以看出來有(text文本段  data 數據段  bss bss段)

其中文本段保存的是可執行文件的指令,數據段保存經過初始化的全局和表態變量,BSS段保存沒有值的變量。但BSS段和前兩個不同,它並不佔據目標文件的任何空間。

下面我們通過一些實例來驗證一下:

1)  寫一個最簡單的”hello world”程序並編譯成a.out

[root@localhost ~]# ls -l a.out

-rwxr-xr-x 1 root root 6664 05-19 17:08 a.out

[root@localhost ~]# size a.out

   text    data     bss     dec     hex filename

   1079     504       8    1591     637 a.out

2)增加一個全局的int[1000]數組聲明,重新編譯:

[root@localhost ~]# ls -l a.out 

-rwxr-xr-x 1 root root 6690 05-19 17:10 a.out

[root@localhost ~]# size a.out  

   text    data     bss     dec     hex filename

   1079     504    4032    5615    15ef a.out

3)現在給數組賦初值。這將使數組從BSS段轉換到數據段:

[root@localhost ~]# ls -l a.out

-rwxr-xr-x 1 root root 10714 05-19 17:13 a.out

[root@localhost ~]# size a.out 

   text    data     bss     dec     hex filename

   1079    4520       8    5607    15e7 a.out

分析:比較第二步和第一步,發現BSS段增加了很多,基本上的數組空間(4*1000),但對比較a.out的大小,沒有很明顯的變化。所以可以看出BSS段並不佔目標文件的空間;再來看第三步的結果,BSS段的大小又回到第一步的大小(因爲全局數組初始化了,從而轉到了數據段),數據段增加了4000多。再來看a.out在大小,是不是也增加了4000多。所以可以得出結論:數據段保存在目標文件中,BSS段不保存在目標文件中。

二、             目標文件和可執行文件是怎麼映射到內存的

前面我們知道了目標文件是以段的形式組織的,那麼爲什麼要這樣做呢?那是因爲段可以方便的地映射到鏈接器在運行時可以直接載入的對象中。載入器只是取文件中的每個段的映像,並直接將它們放入到內存,從本質上說,段在正在執行的程序中是一塊內存區域,每個段都有特定的目的,下圖是一個各個段和內存的簡單的映射關係,接下聯繫這個圖逐個的介紹它們的作用

 

1)      文本段,文件段包含程序的指令。鏈接器把指令直接從文件拷貝到內存中(一般使用mmap()系統調用),然後便再也不用管它,每個段的內容還可以賦予不同的屬性,比如文件段一般是隻允許讀和執行,數據段有些是允許讀和寫,但不允許執行;還有一些可能是隻讀。

2)      數據段,數據段包括經過初始化的全局和靜態變量以及他們的值,

3)      BSS段,BSS段的大小從可執行文件中得到,然後鏈接器得到這個大小的內存塊。緊跟在數據段之後,包括數據段和BSS段整個區域統稱爲數據區,這是因爲在操作系統的內存管理術語中,段就是一片連續的虛擬地址地址。

4)      從上圖中可以看到,一個即將執行的程序可能還需要一些內存空間,用於保存局部變量、臨時變量、傳遞到函數中的參數。堆棧段就是用於這個目的,另外還需要堆(heap)空間,用於動態分配內存

三、             堆棧段有哪些具體作用

前面已經說了堆棧段是用於保存局部變量、臨時變量、傳遞到函數中的參數等的。它其實包含了一種單一的數據結構——堆棧,那麼堆棧又怎麼理解呢?堆棧是實現“先進後出”的結構。打個生活中的比方:好比自助餐廳裏疊在一起的盤子,盤子可以放置任意數量(不用考慮它會倒)。但你只能從頂部放或取一個盤子。也就是說你可以在頂部加盤子,也可以取出。堆棧有點不同的是。你不止可以取頂部“盤子”的值。還可修改頂部的“盤子”的值,還可以修改任意位置盤子的值。那麼它到底有什麼具體的用處呢?

1)  堆棧爲函數內部聲明的局部變量提供存儲空間,

2)  進行函數調用時,堆棧存儲與此有關的一些維護性信息,了稱之爲“過程活動記錄”,詳情在後一節討論

3)  堆棧也可以補用作暫時存儲區。如一個比較長的算術表達式,可能會把部分結果壓到堆棧中。當需要的時候再把它從中取出。另,遞歸調用是用肯定全用到堆棧的。具體怎麼回事也在後面討論。

四、             當函數調用時的“過程活動記錄”

那麼什麼是過程活動記錄呢?過程活動記錄是一種數據結構,用於支持過程調用,並記錄調用結束以後返回調用點所需要的全部信息,也就是在你調用了函數後。知道此函數什麼返回到什麼地方。舉個例子來說明:

 A(int i){

       If(I <>0)

              A(--i)

       Else

              Printf(“I has reached zero”);

       Return;

}

Int main(){

       A(1);

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章