內存分配及清空、調試

1. 存分配的三個方法:php

(1)      從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。html

(2)      在棧上建立。在執行函數時,函數內局部變量的存儲單元均可以在棧上建立,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置於處理器的指令集中,效率很高,可是分配的內存容量有限。程序員

(3)      從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員本身負責在什麼時候用free或delete釋放內存。動態內存的生存期由咱們決定,使用很是靈活,但問題也最多編程

                                            -----摘自《高質量C++編程》數組

2.calloc(), malloc(), realloc(), free()數據結構

void *calloc(size_t nobj, size_t size);函數

配足夠的內存給nobj個spa

大小爲size的對象組成的數組, 並返回指向所分配區域的第一個字節的指針;
若內存不夠,則返回NULL. 該空間的初始化大小爲0字節操作系統

char *p = (char *) calloc(100, sizeof(char));.net

 

void *malloc(size_t size);
分配足夠的內存給大小爲size的對象, 並返回指向所分配區域的第一個字節的指針;
若內存不夠,則返回NULL. 不對分配的空間進行初始化.
char *p = (char *) malloc(sizeof(char));

 

void *realloc(void *p, size_t size);

將p所指向的對象的大小改成size個字節.
若是新分配的內存比原內存大, 那麼原內存的內容保持不變, 增長的空間不進行初始化.
若是新分配的內存比原內存小, 那麼新內存保持原內存的內容, 增長的空間不進行初始化.
返回指向新分配空間的指針; 若內存不夠,則返回NULL, 原p指向的內存區不變.

char *p = (char *) malloc(sizeof(char));
p= (char *) realloc(p, 256);

 

void free(void *p);

釋放p所指向的內存空間; 當p爲NULL時, 不起做用.
p必先調用calloc, malloc或realloc.

free(p);

3.New和delete

在C++中,操做符new 用於申請內存,操做符delete 用於釋放內存

new 能比malloc 幹更多的事,它能夠申請對象的內存

若是是用new申請的內存,則必須用delete 而不能用free 來釋放。若是是用malloc 申請的內存,則必須用free 而不能用delete 來釋放。在用delete 或用free 釋放p 所指的內存後,應該立刻顯式地將p 置爲NULL,以防下次使用p 時發生錯誤。

 

float  *p;

p = new float[100];

if(p == NULL)

{

return;

}

^

^

delete [ ]p;//別忘記括號

p = NULL;

 

int *pNumber;

pNumber = new int;

^^^^^^^

delete pNumber;

pNumber = NULL;

 

double *pDouble;

pDouble = new double;

^^^^^^

delete pNumber;

pNumber = NULL;

 

CString *string;

String = new CString;

 

 

delete string;

string = NULL; 

 

//複製,數組……

char a[]=」Hello Word!」;

char b[10];

strcpy(b,a);

if (strcmp(a,b)==0)

{

}

 

//指針……

char a[]=」Hello Word!」;

char *p;

p=new char[strlen(a)+1];

if (strcmp(p,a)==0)

{}

4.防止指針出錯:

l         【規則7-2-1】用malloc或new申請內存以後,應該當即檢查指針值是否爲NULL。防止使用指針值爲NULL的內存。

l         【規則7-2-2】不要忘記爲數組和動態內存賦初值。防止將未被初始化的內存做爲右值使用。

l         【規則7-2-3】避免數組或指針的下標越界,特別要小心發生「多1」或者「少1」操做。

l         【規則7-2-4】動態內存的申請與釋放必須配對,防止內存泄漏。

【規則7-2-5】用free或delete釋放了內存以後,當即將指針設置爲NULL,防止產生「野指針」。

5.關於sizeof

char a[]=」Hello World!」

char *p=a;

count<<sizeof(a)<<end; //12字節

count<<sizeof(p)<<endl; //4字節

 

並且,在函數中,數組參數退化爲指針

void fun(char a[1000])

{

count<<sizeof(a)<<endl; //輸出4而不是1000

}   

6.一些內存操做函數:

void *memset(void *s,int c,int n)

用c填充由指針s指向的內存區域的前n個字節.返回指向該內存區域的指針s.s並不必定是指向字符的指針,他能夠是指向任何類型的指針,甚至能夠是指向結構的指針.

void *memcpy(void *dest,const void *src,int n);

將指針src指向的前n個字節拷貝到dest指向的前n個內存區域中。若是src和dest有重複區域,則會被覆蓋.即在拷貝的過程當中向後移.這樣可能達不到預期的效果

void *memmove(void *dest,const void *src,int n)

該函數和前面函數的 區別是當src和desc有重複區域時,則會先將desc向後移,而後再進行拷貝操做

VOID ZeroMemory( PVOID Destination, DWORD Length )

;//內存數據清0

 

7.全局內存分配

HGLOBAL GlobalAlloc(UINT uFlags, DWORD dwBytes  );

WFlags:

 

 

GMEM_FIXED

 

 

分配一個固定內存塊

 

 

GMEM_MOVEABLE

 

 

分配一個可移動內存塊

 

 

GMEM_DISCARDABLE

 

 

分配一個可丟棄內存塊

 

 

GMEM_NOCOMPACT

 

 

堆在這個函數調用期間不進行累積

 

 

GMEM_NODISCARD

 

 

函數調用期間不丟棄任何內存塊

 

 

GMEM_ZEROINIT

 

 

新分配的內存塊所有初始化成零

 

 

dwBytes  :

要分配的字符數

如指定了 GMEM_FIXED,那麼返回值就是要使用的實際內存地址(GlobalLock 會返回一樣的值)——因此在使用固定內存塊的時候不須要執行一個 GlobalLock/GlobalUnlock 操做

HGLOBAL GlobalFree(HGLOBAL hMem);   // handle to the global memory object

釋放指定的全局內存塊。在調用了這個函數之後,hMem 句柄就再也不有效。注意調用這個函數的時候,內存塊不會進入鎖定狀態

內存釋放後,向那個內存塊寫入數據的任何企圖均可能形成進程堆的崩潰,致使嚴重的異常錯誤

LPVOID GlobalLock(

  HGLOBAL hMem   // handle to the global memory object

);

鎖定內存中指定的內存塊,並返回一個地址值,令其指向內存塊的起始處。除非用 GlobalUnlock 函數將內存塊解鎖,不然地址會一直保持有效。Windows 爲每一個內存對象都維持着一個鎖定計數。對這個函數的每次調用都應有一個對應的 GlobalUnlock 調用

 

BOOL GlobalUnlock(

  HGLOBAL hMem   // handle to the global memory object

);

8.調試

------摘自http://www.vczx.com/tutorial/mfc/mfc10.php

1.MFC應用程序可使用C運行庫的調試手段,也可使用MFC提供的調試手段。兩種調試手段分別論述以下。

C運行庫提供和支持的調試功能

C運行庫提供和支持的調試功能以下:

調試信息報告函數

用來報告應用程序的調試版本運行時的警告和出錯信息。包括:

_CrtDbgReport 用來報告調試信息;

_CrtSetReportMode 設置是否警告、出錯或者斷言信息;

_CrtSetReportFile 設置是否把調試信息寫入到一個文件。

條件驗證或者斷言宏:

斷言宏主要有:

assert 檢驗某個條件是否知足,不知足終止程序執行。

驗證函數主要有:

_CrtIsValidHeapPointer 驗證某個指針是否在本地堆中;

_CrtIsValidPointer 驗證指定範圍的內存是否能夠讀寫;

_CrtIsMemoryBlock 驗證某個內存塊是否在本地堆中。

內存(堆)調試:

malloc_dbg 分配內存時保存有關內存分配的信息,如在什麼文件、哪一行分配的內存等。有一系列用來提供內存診斷的函數:

_CrtMemCheckpoint 保存內存快照在一個_CrtMemState結構中;

_CrtMemDifference 比較兩個_CrtMemState;

_CrtMemDumpStatistics 轉儲輸出一_CrtMemState結構的內容;

_CrtMemDumpAllObjectsSince 輸出上次快照或程序開始執行以來在堆中分配的全部對象的信息;

_CrtDumpMemoryLeaks 檢測程序執行以來的內存漏洞,若是有漏洞則輸出全部分配的對象。

 

9.虛擬內存技術原理和使用方法

-------摘自http://www3.ccw.com.cn/club/bbs/showannounce.asp?id=861126

Windows的內存結構是深刻理解Windows操做系統如何運做的最關鍵之所在,經過對內存結構的認識可清楚地瞭解諸如進程間數據的共享、對內存進行有效的管理等問題,從而可以在程序設計時使程序以更加有效的方式運行。Windows操做系統對內存的管理可採起多種不一樣的方式,其中虛擬內存的管理方式可用來管理大型的對象和結構數組。

  在Windows系統中,任何一個進程都被賦予其本身的虛擬地址空間,該虛擬地址空間覆蓋了一個至關大的範圍,對於32位進程,其地址空間爲232=4,294,967,296 Byte,這使得一個指針可使用從0x00000000到0xFFFFFFFF的4GB範圍以內的任何一個值。雖然每個32位進程可以使用4GB的地址空間,但並不意味着每個進程實際擁有4GB的物理地址空間,該地址空間僅僅是一個虛擬地址空間,此虛擬地址空間只是內存地址的一個範圍。進程實際能夠獲得的物理內存要遠小於其虛擬地址空間。進程的虛擬地址空間是爲每一個進程所私有的,在進程內運行的線程對內存空間的訪問都被限制在調用進程以內,而不能訪問屬於其餘進程的內存空間。這樣,在不一樣的進程中可使用相同地址的指針來指向屬於各自調用進程的內容而不會由此引發混亂。下面分別對虛擬內存的各具體技術進行介紹。

  地址空間中區域的保留與釋放

  在進程建立之初並被賦予地址空間時,其虛擬地址空間還沒有分配,處於空閒狀態。這時地址空間內的內存是不能使用的,必須首先經過VirtualAlloc()函數來分配其內的各個區域,對其進行保留。VirtualAlloc()函數原型爲:

LPVOID VirtualAlloc(

 LPVOID lpAddress,

 DWORD dwSize,

 DWORD flAllocationType,

 DWORD flProtect

);

  其參數lpAddress包含一個內存地址,用於定義待分配區域的首地址。一般可將此參數設置爲NULL,由系統經過搜索地址空間來決定知足條件的未保留地址空間。這時系統可從地址空間的任意位置處開始保留一個區域,並且還能夠經過向參數flAllocationType設置MEM_TOP_DOWN標誌來指明在儘量高的地址上分配內存。若是不但願由系統自動完成對內存區域的分配而爲lpAddress設定了內存地址(必須確保其始終位於進程的用戶模式分區中,不然將會致使分配的失敗),那麼系統將在進行分配以前首先檢查在該內存地址上是否存在足夠大的未保留空間,若是存在一個足夠大的空閒區域,那麼系統將會保留此區域並返回此保留區域的虛擬地址,不然將致使分配的失敗而返回NULL。這裏須要特別指出的是,在指定lpAddress的內存地址時,必須確保是從一個分配粒度的邊界處開始。

  通常來講,在不一樣的CPU平臺下分配粒度各不相同,但目前全部Windows環境下的CPU如x8六、32位Alpha、64位Alpha以及IA-64等均是採用64KB的分配粒度。若是保留區域的起始地址沒有遵循從64KB分配粒度的邊界開始之一原則,系統將自動調整該地址到最接近的64K的倍數。例如,若是指定的lpAddress爲0x00781022,那麼此保留區域實際是從0x00780000開始分配的。參數dwSize指定了保留區域的大小。可是系統實際保留的區域大小必須是CPU頁面大小的整數倍,若是指定的dwSize並不是CPU頁面的整數倍,系統將自動對其進行調整,使其達到與之最接近的頁面大小整數倍。與分配粒度同樣,對於不一樣的CPU平臺其頁面大小也是不同的。在x86平臺下,頁面大小爲4KB,在32位Alpah平臺下,頁面大小爲8KB。在使用時能夠經過GetSystemInfo()來決定當前主機的頁面大小。參數flAllocationType和flProtect分別定義了分配類型和訪問保護屬性。因爲VirtualAlloc()可用來保留一個區域也能夠用來佔用物理存儲器,所以經過flAllocationType來指定當前要保留的是一個區域仍是要佔用物理存儲器是意義的。其可能使用的內存分配類型有:

分配類型  類型說明

MEM_COMMIT 爲特定的頁面區域分配內存中或磁盤的頁面文件中的物理存儲

MEM_PHYSICAL  分配物理內存(僅用於地址窗口擴展內存)

MEM_RESERVE 保留進程的虛擬地址空間,而不分配任何物理存儲。保留頁面可經過繼續調用VirtualAlloc()而被佔用

MEM_RESET  指明在內存中由參數lpAddress和dwSize指定的數據無效

MEM_TOP_DOWN 在儘量高的地址上分配內存(Windows 98忽略此標誌)

MEM_WRITE_WATCH 必須與MEM_RESERVE一塊兒指定,使系統跟蹤那些被寫入分配區域的頁面(僅針對Windows 98)

  分配成功完成後,即在進程的虛擬地址空間中保留了一個區域,能夠對此區域中的內存進行保護權限許可範圍內的訪問。當再也不須要訪問此地址空間區域時,應釋放此區域。由VirtualFree()負責完成。其函數原型爲:

BOOL VirtualFree(

 LPVOID lpAddress,

 DWORD dwSize,

 DWORD dwFreeType

);

  其中,參數lpAddress爲指向待釋放頁面區域的指針。若是參數dwFreeType指定了MEM_RELEASE,則lpAddress必須爲頁面區域被保留時由VirtualAlloc()所返回的基地址。參數dwSize指定了要釋放的地址空間區域的大小,若是參數dwFreeType指定了MEM_RELEASE標誌,則將dwSize設置爲0,由系統計算在特定內存地址上的待釋放區域的大小。參數dwFreeType爲所執行的釋放操做的類型,其可能的取值爲MEM_RELEASE和MEM_DECOMMIT,其中MEM_RELEASE標誌指明要釋放指定的保留頁面區域,MEM_DECOMMIT標誌則對指定的佔用頁面區域進行佔用的解除。若是VirtualFree()成功執行完成,將回收所有範圍的已分配頁面,此後如再對這些已釋放頁面區域內存的訪問將引起內存訪問異常。釋放後的頁面區域可供系統繼續分配使用。

  下面這段代碼演示了由系統在進程的用戶模式分區內保留一個64KB大小的區域,並將其釋放的過程:

// 在地址空間中保留一個區域

LPBYTE bBuffer = (LPBYTE)VirtualAlloc(NULL, 65536, MEM_RESERVE, PAGE_READWRITE);

……

// 釋放已保留的區域

VirtualFree(bBuffer, 0, MEM_RELEASE);

物理存儲器的提交與回收

  在地址空間中保留一個區域後,並不能直接對其進行使用,必須在把物理存儲器提交給該區域後,才能夠訪問區域中的內存地址。在提交過程當中,物理存儲器是按頁面邊界和頁面大小的塊來進行提交的。若要爲一個已保留的地址空間區域提交物理存儲器,須要再次調用VirtualAlloc()函數,所不一樣的是在執行物理存儲器的提交過程當中須要指定flAllocationType參數爲MEM_COMMIT標誌,使用的保護屬性與保留區域時所用保護屬性一致。在提交時,能夠將物理存儲器提交給整個保留區域,也能夠進行部分提交,由VirtualAlloc()函數的lpAddress參數和dwSize參數指明要將物理存儲器提交到何處以及要提交多少物理存儲器。

  與保留區域的釋放相似,當再也不須要訪問保留區域中被提交的物理存儲器時,提交的物理存儲器應獲得及時的釋放。該回收過程與保留區域的釋放同樣也是經過VirtualFree()函數來完成的。在調用時爲VirtualFree()的dwFreeType參數指定MEM_DECOMMIT標誌,並在參數lpAddress和dwSize中傳遞用來標識要解除的第一個頁面的內存地址和要釋放的字節數。此回收過程一樣也是以頁面爲單位來進行的,將回收設定範圍所涉及到的全部頁面。下面這段代碼演示了對先前保留區域的提交過程,並在使用完畢後將其回收:

// 在地址空間中保留一個區域

LPBYTE bBuffer = (LPBYTE)VirtualAlloc(NULL, 65536, MEM_RESERVE, PAGE_READWRITE);

// 提交物理存儲器

VirtualAlloc(bBuffer, 65536, MEM_COMMIT, PAGE_READWRITE);

……

// 回收提交的物理存儲器

VirtualFree(bBuffer, 65536, MEM_DECOMMIT);

// 釋放已保留的區域

VirtualFree(bBuffer, 0, MEM_RELEASE);

  因爲未經提交的保留區域實際是沒法使用的,所以在編程過程當中容許經過一次VirtualAlloc()調用而完成對地址空間的區域保留及對保留區域的物理存儲器的提交。相應的,回收、釋放過程也可由一次VirtualFree()調用來實現。上述代碼可按此方法改寫爲:

// 在地址空間中保留一個區域並提交物理存儲器

LPBYTE bBuffer = (LPBYTE)VirtualAlloc(NULL, 65536, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);

……

// 釋放已保留的區域並回收提交的物理存儲器

VirtualFree(bBuffer, 0, MEM_RELEASE | MEM_DECOMMIT);  

  頁文件的使用

  在前面曾屢次提到物理存儲器,這裏所說的物理存儲器並不侷限於計算機內存,還包括在磁盤空間上建立的頁文件,其存儲空間大小爲計算機內存和頁文件存儲容量之和。因爲一般狀況下磁盤存儲空間要遠大於內存的存儲空間,所以頁文件的使用對於應用程序而言至關於透明的增長了其所能使用的內存容量。在使用時,由操做系統和CPU負責對頁文件進行維護和協調。只有在應用程序須要時才臨時將頁文件中的數據加載到內存供應用程序訪問之用,在使用完畢後再從內存交換回頁文件。

  進程中的線程在訪問位於已提交物理存儲器的保留區域的內存地址時,若是此地址指向的數據當前已存在於內存,CPU將直接將進程的虛擬地址映射爲物理地址,並完成對數據的訪問;若是此數據是存在於頁文件中的,就要試圖將此數據從頁文件加載到內存。在進行此處理時,首先要檢查內存中是否有可供使用的空閒頁面,若是有就能夠直接將數據加載到內存中的空閒頁面,不然就要從內存中尋找一個暫不使用的可釋放的頁面並將數據加載到此頁面。若是被釋放頁面中的數據仍爲有效數據(即之後還會用到),就要先將此頁面從內存寫入到頁文件。在數據加載到內存後,仍要在CPU將虛擬地址映射爲物理地址後方可實現對數據的訪問。與對物理存儲器中數據的訪問有所不一樣,在運行可執行程序時並不進行程序代碼和數據的從磁盤文件到頁文件的複製過程,而是在肯定了程序的代碼及其數據的大小後,由系統直接將可執行程序的映像用做程序的保留地址空間區域。這樣的處理方式大大縮短了程序的啓動時間,並可減少頁文件的尺寸。

對內存的管理

  使用虛擬內存技術將可以對內存進行管理。對當前內存狀態的動態信息可經過GlobalMemoryStatus()函數來獲取。GlobalMemoryStatus()的函數原型爲:

VOID GlobalMemoryStatus(LPMEMORYSTATUS lpBuffer);

  其參數lpBuffer爲一個指向內存狀態結構MEMORYSTATUS的指針,並且要預先對該結構對象的數據成員進行初始化。MEMORYSTATUS結構定義以下:

typedef struct _MEMORYSTATUS {

 DWORD dwLength; // MEMORYSTATUS結構大小

 DWORD dwMemoryLoad; // 已使用內存所佔的百分比

 DWORD dwTotalPhys; // 物理存儲器的總字節數

 DWORD dwAvailPhys; // 空閒物理存儲器的字節數

 DWORD dwTotalPageFile; // 頁文件包含的最大字節數

 DWORD dwAvailPageFile; // 頁文件可用字節數

 DWORD dwTotalVirtual; // 用戶模式分區大小

 DWORD dwAvailVirtual; // 用戶模式分區中空閒內存大小

} MEMORYSTATUS, *LPMEMORYSTATUS;

下面這段代碼經過設置一個定時器而每隔5秒更新一次當前系統對內存的使用狀況:

// 設置定時器

SetTimer(0, 5000, NULL);

……

void CSample22Dlg::OnTimer(UINT nIDEvent)

{

 // 獲取當前內存使用狀態

 MEMORYSTATUS mst;

 GlobalMemoryStatus(&mst);

 // 已使用內存所佔的百分比

 m_dwMemoryLoad = mst.dwMemoryLoad;

 // 物理存儲器的總字節數

 m_dwAvailPhys = mst.dwAvailPhys / 1024;

 // 空閒物理存儲器的字節數

 m_dwAvailPageFile = mst.dwAvailPageFile / 1024;

 // 頁文件包含的最大字節數

 m_dwAvailVirtual = mst.dwAvailVirtual / 1024;

 // 頁文件可用字節數

 m_dwTotalPageFile = mst.dwTotalPageFile / 1024;

 // 用戶模式分區大小

 m_dwTotalPhys = mst.dwTotalPhys / 1024;

 // 用戶模式分區中空閒內存大小

 m_dwTotalVirtual = mst.dwTotalVirtual / 1024;

 // 更新顯示

 UpdateData(FALSE);

 CDialog::OnTimer(nIDEvent);

}

  對內存的管理除了對當前內存的使用狀態信息進行獲取外,還常常須要獲取有關進程的虛擬地址空間的狀態信息。可由VirtualQuery()函數來進行查詢,其原型聲明以下:

DWORD VirtualQuery(

 LPCVOID lpAddress, // 內存地址

 PMEMORY_BASIC_INFORMATION lpBuffer, // 指向內存信息結構的指針

 DWORD dwLength // 內存的大小

);

  其中lpAddress參數爲要查詢的虛擬內存地址,該值將被調整到最近的頁邊界處。當前計算機的頁面大小可經過GetSystemInfo()函數獲取,該函數須要一個指向SYSTEM_INFO結構的指針做爲參數,獲取到的系統信息將填充在該數據結構對象中。下面這段代碼經過對GetSystemInfo()的調用而獲取了當前的系統信息:

// 獲得當前系統信息

GetSystemInfo(&m_sin);

// 位屏蔽,指明哪一個CPU是活動的

m_dwActiveProcessorMask = m_sin.dwActiveProcessorMask;

// 保留的地址空間區域的分配粒度

m_dwAllocationGranularity = m_sin.dwAllocationGranularity;

// 進程的可用地址空間的最小內存地址

m_dwMaxApplicationAddress = (DWORD)m_sin.lpMaximumApplicationAddress;

// 進程的可用地址空間的最大內存地址

m_dwMinApplicationAddress = (DWORD)m_sin.lpMinimumApplicationAddress;

// 計算機中CPU的數目

m_dwNumberOfProcessors = m_sin.dwNumberOfProcessors;

// 頁面大小

m_dwPageSize = m_sin.dwPageSize;

// 處理器類型

m_dwProcessorType = m_sin.dwProcessorType;

//進一步細分處理器級別

m_wProcessorLevel = m_sin.wProcessorLevel;

// 系統處理器的結構

m_wProcessorArchitecture = m_sin.wProcessorArchitecture;

// 更新顯示

UpdateData(FALSE);

VirtualQuery()的第二個參數lpBuffer爲一個指向MEMORY_BASIC_INFORMATION結構的指針。VirtualQuery()如成功執行,該結構對象中將保存查詢到的虛擬地址空間狀態信息。MEMORY_BASIC_INFORMATION結構的定義爲:

typedef struct _MEMORY_BASIC_INFORMATION {

 PVOID BaseAddress; // 保留區域的基地址

 PVOID AllocationBase; // 分配的基地址

 DWORD AllocationProtect; // 初次保留時所設置的保護屬性

 DWORD RegionSize; // 區域大小

 DWORD State; // 狀態(提交、保留或空閒)

 DWORD Protect; // 當前訪問保護屬性

 DWORD Type; // 頁面類型

} MEMORY_BASIC_INFORMATION;  

  經過VirtualQuery()函數對由lpAddress和dwLength參數指定的虛擬地址空間區域的查詢而獲取獲得的相關狀態信息:

// 更新顯示

UpdateData(TRUE);

// 虛擬地址空間狀態結構

MEMORY_BASIC_INFORMATION mbi;

// 查詢指定虛擬地址空間的狀態信息

VirtualQuery((LPCVOID)m_dwAddress, &mbi, 1024);

// 保留區域的基地址

m_dwBaseAddress = (DWORD)mbi.BaseAddress;

// 分配的基地址

m_dwAllocateBase = (DWORD)mbi.AllocationBase;

// 初次保留時所設置的保護屬性

m_dwAllocateProtect = mbi.AllocationProtect;

// 區域大小

m_dwRegionSize = mbi.RegionSize;

// 狀態(提交、保留或空閒)

m_dwState = mbi.State;

// 當前訪問保護屬性

m_dwProtect = mbi.Protect;

// 頁面類型

m_dwType = mbi.Type;

// 更新顯示

UpdateData(FALSE);

7. 堆中分配內存的一些函數

--------摘自http://computer.sz.net.cn/2004-03-31/nw2004033100068.shtml

    (1). 在進程中,若是須要能夠在原有默認堆的基礎上動態建立一個堆,可由

HeapCreate()函數完成:

HANDLE HeapCreate(

 DWORD flOptions,

 DWORD dwInitialSize,

 DWORD dwMaximumSize

);

其第一個參數flOptions指定了對新建堆的操做屬性。該標誌將會影響一些堆函數如HeapAlloc()、HeapFree()、HeapReAlloc()和HeapSize()等對新建堆的訪問。其可能的取值爲下列標誌及其組合:

 

 

屬性標誌

 

 

說明

 

 

HEAP_GENERATE_EXCEPTIONS

 

 

在遇到因爲內存越界等而引發的函數失敗時,由系統拋出一個異常來指出此失敗,而不是簡單的返回NULL指針。

 

 

HEAP_NO_SERIALIZE

 

 

指明互斥現象不會出現

 

 

參數dwInitialSize和dwMaximumSize分別爲堆的初始大小和堆棧的最大尺寸。其中,dwInitialSize的值決定了最初提交給堆的字節數。若是設置的數值不是頁面大小的整數倍,則將被圓整到鄰近的頁邊界處。而dwMaximumSize則其實是系統能爲堆保留的地址空間區域的最大字節數。若是該值爲0,那麼將建立一個可擴展的堆,堆的大小僅受可用內存的限制。若是應用程序須要分配大的內存塊,一般要將該參數設置爲0。若是dwMaximumSize大於0,則該值限定了堆所能建立的最大值,HeapCreate()一樣也要將該值圓整到鄰近的頁邊界,而後再在進程的虛擬地址空間爲堆保留該大小的一塊區域。在這種堆中分配的內存塊大小不能超過0x7FFF8字節,任何試圖分配更大內存塊的行爲將會失敗,即便是設置的堆大小足以容納該內存塊。若是HeapCreate()成功執行,將會返回一個標識新堆的句柄,並可供其餘堆函數使用。

(2). 在成功建立一個堆後,能夠調用HeapAlloc()函數從堆中分配內存塊。在此,除了能夠從用HeapCreate()建立的動態堆中分配內存塊,也能夠直接從進程的默認堆中分配內存塊。下面先給出HeapAlloc()的函數原型:

LPVOID HeapAlloc(

 HANDLE hHeap,

 DWORD dwFlags,

 DWORD dwBytes

);

其中,參數hHeap爲要分配的內存塊來自的堆的句柄,能夠是從HeapCreate()建立的動態堆句柄也能夠是由GetProcessHeap()獲得的默認堆句柄。參數dwFlags指定了影響堆分配的各個標誌。該標誌將覆蓋在調用HeapCreate()時所指定的相應標誌,可能的取值爲:

 

 

標誌

 

 

說明

 

 

HEAP_GENERATE_EXCEPTIONS

 

 

該標誌指定在進行諸如內存越界操做等狀況時將拋出一個異常而不是簡單的返回NULL指針

 

 

HEAP_NO_SERIALIZE

 

 

強制對HeapAlloc()的調用將與訪問同一個堆的其餘線程不按照順序進行

 

 

HEAP_ZERO_MEMORY

 

 

若是使用了該標誌,新分配內存的內容將被初始化爲0

 

 

    最後一個參數dwBytes設定了要從堆中分配的內存塊的大小。若是HeapAlloc()執行成功,將會返回從堆中分配的內存塊的地址。若是因爲內存不足或是其餘一些緣由而引發HeapAlloc()函數的執行失敗,將會引起異常。經過異常標誌能夠獲得引發內存分配失敗的緣由:若是爲STATUS_NO_MEMORY則代表是因爲內存不足引發的;若是是STATUS_ACCESS_VIOLATION則表示是因爲堆被破壞或函數參數不正確而引發分配內存塊的嘗試失敗。以上異常只有在指定了HEAP_GENERATE_EXCEPTIONS標誌時纔會發生,若是沒有指定此標誌,在出現相似錯誤時HeapAlloc()函數只是簡單的返回NULL指針。

    在設置dwFlags參數時,若是先前用HeapCreate()建立堆時曾指定過HEAP_GENERATE_EXCEPTIONS標誌,就沒必要再去設置HEAP_GENERATE_EXCEPTIONS標誌了,由於HEAP_GENERATE_EXCEPTIONS標誌已經通知堆在不能分配內存塊時將會引起異常。另外,對HEAP_NO_SERIALIZE標誌的設置應慎重,與在HeapCreate()函數中使用HEAP_NO_SERIALIZE標誌相似,若是在同一時間有其餘線程使用同一個堆,那麼該堆將會被破壞。若是是在進程默認堆中進行內存塊的分配則要絕對禁用此標誌。

在使用堆函數HeapAlloc()時要注意:堆在內存管理中的使用主要是用來分配一些較小的數據塊,若是要分配的內存塊在1MB左右,那麼就不要再使用堆來管理內存了,而應選擇虛擬內存的內存管理機制。

(3). 在程序設計時常常會因爲開始時預見不足而形成在堆中分配的內存塊大小的不合適(多數狀況是開始時分配的內存較小,然後來實際須要更多的數據複製到內存塊中去)這就須要在分配了內存塊後再根據須要調整其大小。堆函數HeapReAlloc()將完成這一功能,其函數原型爲:

LPVOID HeapReAlloc(

 HANDLE hHeap,

 DWORD dwFlags,

 LPVOID lpMem,

 DWORD dwBytes

);

其中,參數hHeap爲包含要調整其大小的內存塊的堆的句柄。dwFlags參數指定了在更改內存塊大小時HeapReAlloc()函數所使用的標誌。其可能的取值爲HEAP_GENERATE_EXCEPTIONS、HEAP_NO_SERIALIZE、HEAP_REALLOC_IN_PLACE_ONLY和HEAP_ZERO_MEMORY,其中前兩個標誌的做用與在HeapAlloc()中的做用相同。HEAP_REALLOC_IN_PLACE_ONLY標誌在內存塊被加大時不移動堆中的內存塊,在沒有設置此標誌的狀況下若是對內存進行增大,那麼HeapReAlloc()函數將有可能將原內存塊移動到一個新的地址。顯然,在設置了該標誌禁止內存快首地址進行調整時,將有可能出現沒有足夠的內存供試圖增大的內存塊使用,對於這種狀況,函數對內存塊增大調整的操做是失敗的,內存塊將仍保留原有的大小和位置。HEAP_ZERO_MEMORY標誌的用處則略有不一樣,若是內存快通過調整比之前大,那麼新增長的那部份內存將被初始化爲0;若是通過調整內存塊縮小了,那麼該標誌將不起任何做用。

函數的最後兩個參數lpMem和dwBytes分別爲指向再分配內存塊的指針和再分配的字節數。若是函數成功執行,將返回新的改變了大小的內存塊的地址。若是在調用時使用了HEAP_REALLOC_IN_PLACE_ONLY標誌,那麼返回的地址將與原內存塊地址相同。若是由於內存不足等緣由而引發函數的執行失敗,函數將返回一個NULL指針。可是HeapReAlloc()的執行失敗並不會影響原內存塊,它將保持原來的大小和位置繼續存在。能夠經過HeapSize()函數來檢索內存塊的實際大小。

(4) 在再也不須要使用堆中的內存塊時,能夠經過HeapFree()將其予以釋放。該函數結構比較簡單,只含有三個參數:
BOOL HeapFree(
 HANDLE hHeap,
 DWORD dwFlags,
 LPVOID lpMem
);
其中,hHeap爲要包含要釋放內存塊的堆的句柄;參數dwFlags爲堆棧的釋放選項能夠是0,也能夠是HEAP_NO_SERIALIZE;最後的參數lpMem爲指向內存塊的指針。若是函數成功執行,將釋放指定的內存塊,並返回TRUE。該函數的主要做用是能夠用來幫助堆管理器回收某些不使用的物理存儲器以騰出更多的空閒空間,可是並不能保證必定會成功。
最後,在程序退出前或是應用程序再也不須要其建立的堆了,能夠調用HeapDestory()函數將其銷燬。該函數只包含一個參數--待銷燬的堆的句柄。HeapDestory()的成功執行將能夠釋放堆中包含的全部內存塊,也可將堆佔用的物理存儲器和保留的地址空間區域所有從新返回給系統並返回TRUE。該函數只對由HeapCreate()顯式建立的堆起做用,而不能銷燬進程的默認堆,若是強行將由GetProcessHeap()獲得的默認堆的句柄做爲參數去調用HeapDestory(),系統將會忽略對該函數的調用。
(5)new 和delete
new與delete內存空間動態分配操做符是C++中使用堆進行內存管理的一種經常使用方式,在程序運行過程當中能夠根據須要隨時經過這兩個操做符創建或刪除堆對象。new操做符將在堆中分配一個足夠大小的內存塊以存放指定類型的對象,若是每次構造的對象類型不一樣,則須要按最大對象所佔用的空間來進行分配。new操做符在成功執行後將返回一個類型與new所分配對象相匹配的指針,若是不匹配則要對其進行強制類型轉換,不然將會編譯出錯。在再也不須要這個對象的時候,必須顯式調用delete操做符來釋放此空間。這一點是很是重要的,若是在預分配的緩衝裏構造另外一個對象以前或者在釋放緩衝以前沒有顯式調用delete操做符,那麼程序將產生不可預料的後果
(6). 小結
在使用堆時有時會形成系統運行速度的減慢,一般是由如下緣由形成的:分配操做形成的速度減慢;釋放操做形成的速度減慢;堆競爭形成的速度減慢;堆破壞形成的速度減慢;頻繁的分配和重分配形成的速度減慢等。其中,競爭是在分配和釋放操做中致使速度減慢的問題。基於上述緣由,建議不要在程序中過於頻繁的使用堆。
相關文章
相關標籤/搜索