內存分配及清空、調試
1. 存分配的三個方法:
(1) 從靜態存儲區域分配。內存在程序編譯的時候就已經分配好,這塊內存在程序的整個運行期間都存在。例如全局變量,static變量。
(2) 在棧上創建。在執行函數時,函數內局部變量的存儲單元都可以在棧上創建,函數執行結束時這些存儲單元自動被釋放。棧內存分配運算內置于處理器的指令集中,效率很高,但是分配的內存容量有限。
(3) 從堆上分配,亦稱動態內存分配。程序在運行的時候用malloc或new申請任意多少的內存,程序員自己負責在何時用free或delete釋放內存。動態內存的生存期由我們決定,使用非常靈活,但問題也最多
-----摘自《高質量C++編程》
2.calloc(), malloc(), realloc(), free()
void *calloc(size_t nobj, size_t size);
配足夠的內存給nobj個
大小為size的對象組成的數組, 并返回指向所分配區域的第一個字節的指針;
若內存不夠,則返回NULL. 該空間的初始化大小為0字節
char *p = (char *) calloc(100, sizeof(char));
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如x86、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). 小結 在使用堆時有時會造成系統運行速度的減慢,通常是由以下原因造成的:分配操作造成的速度減慢;釋放操作造成的速度減慢;堆競爭造成的速度減慢;堆破壞造成的速度減慢;頻繁的分配和重分配造成的速度減慢等。其中,競爭是在分配和釋放操作中導致速度減慢的問題。基于上述原因,建議不要在程序中過于頻繁的使用堆。RFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成