C++內存分配淺析

一、堆內存的分配
32位操作系統支持4GB內存的連續訪問,但通常把內存分爲兩個2GB的空間,每個進程在運行時最大可以使用2GB的私有內存(0x00000000—0x7FFFFFFF)。即理論上支持如下的大數組:
char szBuffer[2*1024*1024*1024];
當然,由於在實際運行時,程序還有代碼段、臨時變量段、動態內存申請等,實際上是不可能用到上述那麼大的數組的。
至於高端的2GB內存地址(0x80000000—0xFFFFFFFF),操作系統一般內部保留使用,即供操作系統內核代碼使用。在Windows和Linux平臺上,一些動態鏈接庫(Windows的dll,Linux的so)以及ocx控件等,由於是跨進程服務的,因此一般也在高2GB內存空間運行。

二、內存分配類型
1.堆區(heap) : 也稱爲動態內存分配。程序在運行時用malloc或new申請的內存,由開發人員自己決定何時使用free或delete釋放內存和生存期。開發人員沒有收回的話,會造成內存泄漏。
2.棧區(stack) : 在執行函數時,函數內局部變量,對象的內部成員變量在棧上創建,生命週期持續到函數結束或對象析構,生命期結束時釋放內存。
3.靜態存儲區 : 存放全局變量和靜態變量,程序結束時由系統釋放,分爲全局初始化區和全局未初始化區。
4.文字常量區 : 存放常量字符串,程序結束時由系統釋放。
5.程序代碼區 : 存放函數體的二進制代碼。

代碼如下:
int nGlobalTest = 0; // 靜態存儲區-全局初始化區
char *pGlobalTest; // 靜態存儲區-全局未初始化區
int main()
{
int nTest; // 棧
char szTest[] = "aaa"; // 棧
char *pTest; // 棧
char *pTest1 = "test"; // "test\0"在常量區,pTest在棧
static int nStaticTest = 0; // 靜態存儲區-全局初始化區
pGlobalTest = (char*)malloc(10); // 10個字節區域在堆區
strcpy(pGlobalTest, "test"); // "test\0"在常量區,
system("pause");
return 0;
}

三、堆和棧的區別
1.管理方式 : 堆由開發人員釋放內存,不釋放容易產生內存泄漏;棧由編譯器自動管理,無需我們手工控制,系統釋放內存。
2.碎片問題 : 對於堆來講,頻繁的內存分配會造成內存空間的不連續,造成大量的內存碎片,使程序效率降低;對於棧來說,不會產生碎片問題,因爲棧是先進後出的隊列,不會出現一個內存塊從棧中間彈出。
3.生長方向 : 對於堆,生長方向是向上的,朝着內存地址增加的方向生長;對於棧,生長方向是向下的,朝着內存地址減小的方向生長。
4.分配方式 : 堆只有動態分配,沒有靜態分配;棧分靜態分配和動態分配。
5.分配效率 : 堆則是 C/C++ 函數庫提供的,它的機制是很複雜的,例如爲了分配一塊內存,庫函數會按照一定的算法在堆內存中搜索可用的足夠大小的空間,如果沒有足夠大小的空間,就有可能調用系統功能去增加程序數據段的內存空間,這樣就有機會分到足夠大小的內存,然後進行返回;棧是機器系統提供的數據結構,計算機會在底層對棧提供支持:分配專門的寄存器存放棧的地址,壓棧出棧都有專門的指令執行,這就決定了棧的效率比較高。顯然,堆的效率比棧要低得多。

四、使用內存遇到的問題
1.動態內存分配失敗
分配內存是,堆資源不足,分配失敗,返回一個空指針,沒有對返回指針進行空判斷。
代碼如下:
int* p = new int(1);
if (p == NULL)
return;
2.指針刪除與堆空間釋放
刪除一個指針p,實際上是刪除了p所指的目標,釋放了所佔的堆空間,不是刪除p本身。釋放 堆空間後,p成了空懸指針,不能再通過P使用該空間,在重新給P賦值之前,也不能再直接使用p。
3.內存泄漏和重複釋放
new與delete 是配對使用的, delete只能釋放堆空間。如果new返回的指針值丟失,則所分配的堆空間無法回收,稱內存泄漏,同一空間重複釋放也是危險的,因爲該空間可能已另分配,所以必須妥善保存new返回的指針,以保證不發生內存泄漏,也必須保證不會重複釋放堆內存空間。
4.動態分配的變量或對象的生命期
無名對象的生命期並不依賴於建立它的作用域,比如在函數中建立的動態對象在函數返回後仍可使用。我們也稱堆空間爲自由空間(free store)就是這個原因。但必須記住釋放該對象所佔堆空間,並只能釋放一次,在函數內建立,而在函數外釋放是一件很容易失控的事,往往會出錯。 

五、使用內存可能引起的BUG
1.改寫錯誤
越過數組邊界寫入數據,在動態分配的內存兩端之外寫入數據,或改寫一些堆管理數據結構。
如:char* p = (cahr*)malloc(10);
p[-1] = 0;
p[10] = 0;
2.錯誤使用指針
在指針賦值之前就用它來引用內存。
向庫函數傳送一個錯誤指針。
對指針進行釋放之後再訪問它的內容(可以先free掉指針,再將它設置爲空)。
3.指針釋放引起的錯誤
同一塊內存釋放兩次
釋放一塊未曾malloc分配的內存
釋放仍在使用中的內存
釋放一個無效的指針

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