內存管理基本概念 html
C程序內存分配 程序員
1. 程序結構,下面列出C語言可執行程序的基本狀況: 算法
[root@localhost Ctest]# ls test -l //test爲一個可執行程序 -rwxr-xr-x 1 root root 4868 Mar 26 08:10 test [root@localhost Ctest]# file test //此文件基本狀況 test: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), for GNU/Linux 2.2.5, dynamically linked (uses shared libs), not stripped [root@localhost Ctest]# size test //此二進制可執行文件結構狀況 //代碼區靜態數據/全局初始化數據區 未初始化數據區 十進制總和 十六進制總和 文件名 text data bss dec hex filename 906 284 4 1194 4aa test能夠看出,此可執行程序在存儲時(沒有調入到內存)分爲代碼區(text)、數據區(data)和未初始化數據區(bss)3個部分。
(1)代碼區(text segment)。存放CPU執行的機器指令(machine instructions)。一般,代碼區是可共享的(即另外的執行程序能夠調用它),由於對於頻繁被執行的程序,只須要在內存中有一份代碼便可。代碼區一般是隻讀的,使其只讀的緣由是防止程序意外地修改了它的指令。另外,代碼區還規劃了局部變量的相關信息。 數組
(2)全局初始化數據區/靜態數據區(initialized data segment/data segment)。該區包含了在程序中明確被初始化的全局變量、靜態變量(包括全局靜態變量和局部靜態變量)和常量數據(如字符串常量)。例如,一個不在任何函數內的聲明(全局數據): 數據結構
int maxcount = 99;使得變量maxcount根據其初始值被存儲到初始化數據區中。
static mincount = 100;這聲明瞭一個靜態數據,若是是在任何函數體外聲明,則表示其爲一個全局靜態變量,若是在函數體內(局部),則表示一個局部靜態變量。另外,若是在函數名前加上static,則表示此函數只能在當前文件中被調用。
(3)未初始化數據區,BSS區(uninitialized data segment),存入的是全局未初始化變量。BSS這個叫法是根據一個早期的彙編運算符而來,這個彙編運算符標誌着一個塊的開始。BSS區的數據在程序開始執行以前被內核初始化爲0或者空指針(NULL)。例如一個不在任何函數內的聲明: 框架
long sum[1000];將變量sum存儲到未初始化數據區。
圖3-1所示爲可執行代碼存儲時結構和運行時結構的對照圖。一個正在運行着的C編譯程序佔用的代碼分爲代碼區、初始化數據區、未初始化數據區,堆區和棧區5個部分。 函數
(1)代碼區(text segment)。代碼區指令根據程序設計流程依次執行,對於順序指令,則只會執行一次(每一個進程),若是反覆,則須要使用跳轉指令,若是進行遞歸,則須要藉助棧來實現。 操作系統
代碼區的指令包括操做碼和要操做的對象(或對象地址引用)。若是是當即數(即具體的數值,如5),將直接包含在代碼中;若是是局部數據,將在棧區分配空間,而後引用該數據地址;若是是BSS區和數據區,在代碼中一樣將引用該數據地址。 設計
(2)全局初始化數據區/靜態數據區(Data Segment)。只初始化一次。 指針
(3)未初始化數據區(BSS)。在運行時改變其值。
(4)棧區(stack)。由編譯器自動分配釋放,存放函數的參數值、局部變量的值等。其操做方式相似於數據結構中的棧。每當一個函數被調用,該函數返回地址和一些關於調用的信息,好比某些寄存器的內容,被存儲到棧區。而後這個被調用的函數再爲它的自動變量和臨時變量在棧區上分配空間,這就是C實現函數遞歸調用的方法。每執行一次遞歸函數調用,一個新的棧框架就會被使用,這樣這個新實例棧裏的變量就不會和該函數的另外一個實例棧裏面的變量混淆。
(5)堆區(heap)。用於動態內存分配。堆在內存中位於bss區和棧區之間。通常由程序員分配和釋放,若程序員不釋放,程序結束時有可能由OS回收。
之因此分紅這麼多個區域,主要基於如下考慮:
一個進程在運行過程當中,代碼是根據流程依次執行的,只須要訪問一次,固然跳轉和遞歸有可能使代碼執行不少次,而數據通常都須要訪問屢次,所以單獨開闢空間以方便訪問和節約空間。
臨時數據及須要再次使用的代碼在運行時放入棧區中,生命週期短。
全局數據和靜態數據有可能在整個程序執行過程當中都須要訪問,所以單獨存儲管理。
堆區由用戶自由分配,以便管理。
下面經過一段簡單的代碼來看看C程序執行時內存分配狀況,相關數據在運行時的位置如註釋所述,
//main.cpp int a = 0; //a在全局已初始化數據區 char *p1; //p1在BSS區(未初始化全局變量) main() { int b; //b在棧區 char s[] = "abc"; //s爲數組變量,存儲在棧區,"abc"爲字符串常量,存儲在已初始化數據區 char *p1,p2; //p一、p2在棧區 char *p3 = "123456"; //123456\0在已初始化數據區,p3在棧區 static int c =0; //C爲全局(靜態)數據,存在於已初始化數據區,另外,靜態數據會自動初始化 p1 = (char *)malloc(10);//分配得來的10個字節的區域在堆區 p2 = (char *)malloc(20);//分配得來的20個字節的區域在堆區 free(p1); free(p2);}
2. 內存分配方式
在C語言中,對象可使用靜態或動態的方式分配內存空間。
靜態分配:編譯器在處理程序源代碼時分配。
動態分配:程序在執行時調用malloc庫函數申請分配。
靜態內存分配是在程序執行以前進行的於是效率比較高,而動態內存分配則能夠靈活的處理未知數目。
靜態與動態內存分配的主要區別以下:
靜態對象是有名字的變量,能夠直接對其進行操做;動態對象是沒有名字的名字,須要經過指針間接地對它進行操做。
靜態對象的分配與釋放由編譯器自動處理;動態對象的分配與釋放必須由程序員顯示地管理,它經過malloc()和free()兩個函數(C++中爲new和delete運算符)來完成。
如下是採用靜態分配方式的例子:
int a=100;
此行代碼指示編譯器分配足夠的存儲區以存放一個整型值,該存儲區與名字a相關聯,並用數值100初始化該存儲區。
如下是採用動態分配方式的例子。
p1 = (char *)malloc(10*sizeof(int));//分配得來得10*4字節的區域在堆區
此行代碼分配了10個int類型的對象,而後返回對象在內存中的地址,接着這個地址被用來初始化指針對象p1,對於動態分配的內存惟一的訪問方式是經過指針間接地訪問,其釋放方法爲:
free(p1);
棧和堆的區別:
棧是由編譯器在須要時分配的,不須要時自動清除的變量存儲區。裏面的變量一般是局部變量、函數參數等。堆是由malloc()函數(C++語言爲new運算符)分配的內存塊,內存釋放由程序員手動控制,在C語言爲free函數完成(C++中爲delete)。堆和棧的主要區別有如下幾點:
(1)管理方式不一樣:
棧編譯器自動管理,無需程序員手工控制;而堆空間的申請釋放工做由程序員控制,容易產生內存泄露;
(2)空間大小不一樣:
棧是向低地址擴展的數據結構,是一塊連續的內存區域。這句話的意思是棧頂的地址和棧的最大容量是系統預先規定好的,當申請的空間超過棧的剩餘空間時,將提示溢出。所以,用戶能從棧得到的空間較小。
堆是向高地址擴展的數據結構,是不連續的內存區域。由於系統是用鏈表來存儲空閒內存地址的,且鏈表的遍歷方向是由低地址向高地址。因而可知,堆得到的空間較靈活,也較大。棧中元素都是一一對應的,不會存在一個內存塊從棧中間彈出的狀況。
(3)是否產生碎片:
對於堆來說,頻繁的malloc/free(new/delete)勢必會形成內存空間的不連續,從而形成大量的碎片,使程序效率下降(雖然程序在退出後操做系統會對內存進行回收管理)。對於棧來說,則不會存在這個問題。
(4)增加方向不一樣:
堆的增加方向是向上的,即向着內存地址增長的方向;棧的增加方向是向下的,即向着內存地址減少的方向。
(5)分配方式不一樣:
堆都是程序中由malloc()函數動態申請分配並由free()函數釋放的;棧的分配和釋放是由編譯器完成的,棧的動態分配由alloca()函數完成,可是棧的動態分配和堆是不一樣的,他的動態分配是由編譯器進行申請和釋放的,無需手工實現。
(6)分配效率不一樣:
棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行。堆則是C函數庫提供的,它的機制很複雜,例如爲了分配一塊內存,庫函數會按照必定的算法在堆內存中搜索可用的足夠大的空間,若是沒有足夠大的空間(多是因爲內存碎片太多),就有須要操做系統來從新整理內存空間,這樣就有機會分到足夠大小的內存,而後返回。顯然,堆的效率要比棧低不少。
http://www.cnblogs.com/TonyEwsn/archive/2010/01/29/1659496.html