1. 程式人生 > >text段、data段(堆和棧)和bss段

text段、data段(堆和棧)和bss段

一、程式的記憶體分配

1.1個由C/C++編譯的程式佔用的記憶體分為以下幾個部分 

1>棧區(stack)——由編譯器自動分配釋放 ,存放函式的引數值,區域性變數的值等。其操作方式類似於資料結構中的棧。
2>堆區(heap)——一般由程式設計師分配釋放, 若程式設計師不釋放,程式結束時可能由OS回收 。注意它與資料結構中的堆是兩回事,分配方式倒是類似於連結串列,呵呵。 
3>全域性區(靜態區)(static)——全域性變數和靜態變數的儲存是放在一塊的,初始化的全域性變數和靜態變數在一塊區域,未初始化的全域性變數和未初始化的靜態變數在相鄰的另一塊區域。程式結束後由系統釋放 。
4>文字常量區——常量字串就是放在這裡的。 程式結束後由系統釋放 。
5>程式程式碼區——存放函式體的二進位制程式碼。

1.2 例子程式 
這是一個前輩寫的,非常詳細 
//main.cpp 
int a = 0; 全域性初始化區 
char *p1; 全域性未初始化區 
main() 

int b; 棧 
char s[] = "abc"; 棧 
char *p2; 棧 
char *p3 = "123456"; 123456\0在常量區,p3在棧上。 
static int c =0; 全域性(靜態)初始化區 
p1 = (char *)malloc(10); 
p2 = (char *)malloc(20); 
//分配得來得10和20位元組的區域就在堆區。 
strcpy(p1, "123456");

//123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"優化成一個地方。

}

    BSS(Block Started by Symbol)這個詞最初是UA-SAP彙編器(United Aircraft Symbolic Assembly Program)中的一個偽指令,用於為符號預留一塊記憶體空間。該彙編器由美國聯合航空公司於20世紀50年代中期為IBM 704大型機所開發,後來BSS這個詞被作為關鍵字引入到了IBM 709和7090/94機型上的標準彙編器FAP(Fortran Assembly Program),用於定義符號並且為該符號預留給定數量的未初始化空間,意為“以符號開始的塊”,“以符號開始的塊”指的是編譯器處理未初始化資料的地方。BSS節不包含任何資料,只是簡單的維護開始和結束的地址,以便記憶體區能在執行時被有效地清零。BSS節在應用程式的二進位制映象檔案中並不存在

   一般C語言的編譯後執行語句都編譯成機器程式碼,儲存在.text段;已初始化的全域性變數和區域性靜態變數都儲存在. data段;未初始化的全域性變數和區域性靜態變數一般放在一個叫.“bss”的段裡。我們知道未初始化的全域性變數和區域性靜態變數預設值都為0,本來它們也可以被放在.data段的,但是因為它們都是0,所以為它們在.data段分配空間並且存放資料0是沒有必要的。程式執行的時候它們的確是要佔記憶體空間的,並且可執行檔案必須記錄所有未初始化的全域性變數和區域性靜態變數的大小總和,記為.bss段。所以.bss段只是為未初始化的全域性變數和區域性靜態變數預留位置而已,BSS段的變數只有名稱和大小卻沒有值,所以它在檔案中也不佔據空間

資料段包含經過初始化的全域性變數以及它們的值。BSS段的大小從可執行檔案中得到 ,然後連結器得到這個大小的記憶體塊,緊跟在資料段後面。當這個記憶體區進入程式的地址空間後全部清零。包含資料段和BSS段的整個區段此時通常稱為資料區。

在採用段式記憶體管理的架構中(比如intel的80x86系統),bss段(Block Started by Symbol segment)通常是指用來存放程式中未初始化的全域性變數的一塊記憶體區域,一般在初始化時bss 段部分將會清零。bss段屬於靜態記憶體分配,即程式一開始就將其清零了。比如,在C語言之類的程式編譯完成之後,已初始化的全域性變數儲存在.data 段中,未初始化的全域性變數儲存在.bss 段中。

在《Programming ground up》裡對.bss的解釋為:There is another section called the .bss. This section is like the data section, except that it doesn’t take up space in the executable.
text和data段都在可執行檔案中(在嵌入式系統裡一般是固化在映象檔案中),由系統從可執行檔案中載入;而bss段不在可執行檔案中,由系統初始化。

各個段的關係

圖1所示為可執行程式碼儲存時結構和執行時結構的對照圖。一個正在執行著的C編譯程式佔用的記憶體,從低地址到高地址分別為:程式碼區、(初始化)資料區、(未初始化)資料區(BSS)、堆區和棧區5個部分。

圖1 C程式的記憶體佈局

(1)程式碼區(textsegment)。程式碼區指令根據程式設計流程依次執行,對於順序指令,則只會執行一次(每個程序),如果反覆,則需要使用跳轉指令,如果進行遞迴,則需要藉助棧來實現。

程式碼區的指令中包括操作碼和要操作的物件(或物件地址引用)。如果是立即數(即具體的數值,如5),將直接包含在程式碼中;如果是區域性資料,將在棧區分配空間,然後引用該資料地址;如果是BSS區和資料區,在程式碼中同樣將引用該資料地址。

(2)全域性初始化資料區/靜態資料區(Data Segment)。只初始化一次。

(3)未初始化資料區(BSS)。在執行時改變其值。

(4)棧區(stack)。由編譯器自動分配釋放,存放函式的引數值、區域性變數的值等。其操作方式類似於資料結構中的棧。每當一個函式被呼叫,該函式返回地址和一些關於呼叫的資訊,比如某些暫存器的內容,被儲存到棧區。然後這個被呼叫的函式再為它的自動變數和臨時變數在棧區上分配空間,這就是C實現函式遞迴呼叫的方法。每執行一次遞迴函式呼叫,一個新的棧框架就會被使用,這樣這個新例項棧裡的變數就不會和該函式的另一個例項棧裡面的變數混淆。

(5)堆區(heap)。用於動態記憶體分配。堆在記憶體中位於bss區和棧區之間。一般由程式設計師分配和釋放,若程式設計師不釋放,程式結束時有可能由OS回收。

之所以分成這麼多個區域,主要基於以下考慮:

一個程序在執行過程中,程式碼是根據流程依次執行的,只需要訪問一次,當然跳轉和遞迴有可能使程式碼執行多次,而資料一般都需要訪問多次,因此單獨開闢空間以方便訪問和節約空間。

臨時資料及需要再次使用的程式碼在執行時放入棧區中,生命週期短。

全域性資料和靜態資料有可能在整個程式執行過程中都需要訪問,因此單獨儲存管理。

堆區由使用者自由分配,以便管理。

總體來說,程式原始碼被編譯以後主要分成兩種段:程式指令和程式資料。程式碼段屬於程式指令,而資料段和.bss段屬於程式資料。很多人可能會有疑問:為什麼要那麼麻煩,把程式的指令和資料的存放分開?混雜地放在一個段裡面不是更加簡單?其實資料和指令分段的好處有很多。主要有如下幾個方面。

程式的指令和資料分開原因

l 一方面是當程式被裝載後,資料和指令分別被對映到兩個虛存區域。由於資料區域對於程序來說是可讀寫的,而指令區域對於程序來說是隻讀的,所以這兩個虛存區域的許可權可以被分別設定成可讀寫和只讀。這樣可以防止程式的指令被有意或無意地改寫。

l 另外一方面是對於現代的CPU來說,它們有著極為強大的快取(Cache)體系。由於快取在現代的計算機中地位非常重要,所以程式必須儘量提高快取的命中率。指令區和資料區的分離有利於提高程式的區域性性。現代CPU的快取一般都被設計成資料快取和指令快取分離,所以程式的指令和資料被分開存放對CPU的快取命中率提高有好處。

l 第三個原因,其實也是最重要的原因,就是當系統中執行著多個該程式的副本時,它們的指令都是一樣的,所以記憶體中只須要儲存一份改程式的指令部分。對於指令這種只讀的區域來說是這樣,對於其他的只讀資料也一樣,比如很多程式裡面帶有的圖示、圖片、文字等資源也是屬於可以共享的。當然每個副本程序的資料區域是不一樣的,它們是程序私有的。不要小看這個共享指令的概念,它在現代的作業系統裡面佔據了極為重要的地位,特別是在有動態連結的系統中,可以節省大量的記憶體。比如我們常用的Windows Internet Explorer 7.0執行起來以後,它的總虛存空間為112 844 KB,它的私有部分資料為15 944 KB,即有96 900 KB的空間是共享部分(資料來源見圖3-2)。如果系統中運行了數百個程序,可以想象共享的方法來節省大量空間。