?? 010.txt
字號:
當可丟棄內(nèi)存塊的鎖定計數(shù)為0時,程序也可以使用GlobalDiscard函數(shù)主動將它丟棄,這和Windows將它丟棄的效果是一樣的:
invoke GlobalDiscard,hMemory
使用內(nèi)存函數(shù)時有兩個地方需要特別注意:
(1)NULL指針的檢測——GlobalAlloc函數(shù)和GlobalLock函數(shù)都可以返回內(nèi)存指針,在使用指針前一定要檢測它的有效性,如果使用了函數(shù)執(zhí)行失敗而返回的NULL指針來訪問數(shù)據(jù),會導致程序越權(quán)訪問不該訪問的地方,從而被Windows毫不留情地終止掉,這就是例子代碼中總是有個if語句來判斷eax是否為NULL的原因。
(2)注意訪問越界問題——越界操作也會引起越權(quán)訪問,千萬不要到超出內(nèi)存塊長度的地方去訪問,例如,使用lstrcpy之類的函數(shù)處理字符串之前,先用lstrlen檢測字符串長度是一個好習慣。
4. 獲取內(nèi)存塊的信息
標準內(nèi)存管理函數(shù)中的其他函數(shù)GlobalFlags,GlobalHandle和GlobalSize用來獲取已分配內(nèi)存塊的一些信息。
GlobalFlags函數(shù)主要用來獲取可移動內(nèi)存塊當前的鎖定計數(shù),也可以用來檢測可丟棄內(nèi)存塊是否已經(jīng)被丟棄。對一個hMemory調(diào)用GlobalFlags函數(shù)如下所示:
invoke GlobalFlags,hMemory
如果不是返回GMEM_INVALID_HANDLE,則表示調(diào)用成功,這時返回值的低8位是內(nèi)存塊的鎖定計數(shù),程序可以用GMEM_LOCKCOUNT對獲取計數(shù)值進行and操作(在Windows.inc頭文件中,GMEM_LOCKCOUNT定義為0ffh):
invoke GlobalFlags,hMemory
and eax,GMEM_LOCKCOUNT
mov dwLockCount,eax
返回值的其他數(shù)據(jù)位可能包含下列標志:
● GMEM_DISCARDABLE 表示內(nèi)存塊是可丟棄內(nèi)存塊。
● GMEM_DISCARDED 表示內(nèi)存塊已經(jīng)被丟棄。
GlobalHandle可以從GlobalLock函數(shù)得到的lpMemory值獲取其對應的hMemory,而GlobalSize函數(shù)可以獲知一個內(nèi)存塊的尺寸。
10.1.4 堆管理函數(shù)
Windows的“堆”分為默認堆和私有堆兩種。默認堆是在程序初始化時由操作系統(tǒng)自動創(chuàng)建的,所有的標準內(nèi)存管理函數(shù)都是在默認堆中申請內(nèi)存的;而私有堆相當于在默認堆中保留了一大塊內(nèi)存,用堆管理函數(shù)可以在這個保留的內(nèi)存塊中分配內(nèi)存。
一個進程的默認堆只有一個,而私有堆可以被創(chuàng)建多個。使用私有堆的缺點是分配和釋放內(nèi)存塊的過程中多了一個掃描堆中的內(nèi)存鏈的過程,所以單從分配內(nèi)存的角度來講,在私有堆中分配內(nèi)存速度似乎要慢一點。
但實際上,有些時候使用私有堆可能更有好處。
首先,可以使用默認堆的函數(shù)有多種,而它們可能在不同的線程中同時對默認堆進行操作,為了保持同步,對默認堆的訪問是順序進行的,也就是說,在同一時間內(nèi)每次只有一個線程能夠分配和釋放默認堆中的內(nèi)存塊。如果兩個線程試圖同時分配默認堆中的內(nèi)存塊,那么只有一個線程能夠進行,另一個線程必須等待第一個線程的內(nèi)存塊分配結(jié)束之后才能繼續(xù)執(zhí)行。而私有堆的空間是預留的,不同線程在不同的私有堆中同時分配內(nèi)存并不會引起沖突,所以整體的運行速度可能更快。
其次,當系統(tǒng)必須在物理內(nèi)存和頁文件之間進行頁面交換的時候,系統(tǒng)的性能會受到很大的影響,在某些情況下,使用私有堆可以防止系統(tǒng)頻繁地在物理內(nèi)存和交換文件之間進行數(shù)據(jù)交換,因為將經(jīng)常訪問的內(nèi)存局限于一個小范圍地址的話,頁面交換就不太可能發(fā)生,把頻繁訪問的大量小塊內(nèi)存放在同一個私有堆中就可以保證它們在內(nèi)存中的位置接近。
再則,使用私有堆也有利于封裝和保護模塊化的程序。當程序包含多個模塊的時候,如果使用標準內(nèi)存管理函數(shù)在默認堆中分配內(nèi)存,那么所有模塊分配的內(nèi)存塊是交叉排列在一起的,如果模塊A中的一個錯誤導致內(nèi)存操作越界,可能會覆蓋掉模塊B使用的內(nèi)存塊,到模塊B執(zhí)行的時候出錯了,我們卻很難發(fā)現(xiàn)錯誤的源頭來自于模塊A。如果讓不同的模塊使用自己的私有堆,那么它們使用的內(nèi)存就會完全隔離開來,雖然越界錯誤仍然可能發(fā)生,但很容易跟蹤和定位。
最后,使用私有堆也使大量內(nèi)存的清理變得方便,在默認堆中分配的內(nèi)存需要一塊塊單獨釋放,但將一個私有堆釋放后,在這個堆里的內(nèi)存就全部被釋放掉了,并不需要預先釋放堆中的每個內(nèi)存塊,這樣非常便于模塊的掃尾工作。
1. 私有堆的創(chuàng)建和釋放
創(chuàng)建私有堆的函數(shù)是HeapCreate:
invoke HeapCreate,flOptions,dwInitialSize,dwMaximumSize
.if eax && (eax < 0c0000000h)
mov hHeap,eax
.endif
flOptions參數(shù)是標志,用來指定堆的屬性,可以指定的屬性有HEAP_NO_SERIALIZE和HEAP_GENERATE_EXCEPTIONS兩種。
HEAP_GENERATE_EXCEPTIONS標志用來指定函數(shù)失敗時的返回值,不指定這個標志的話,函數(shù)失敗時返回NULL,否則返回一個具體的出錯代碼,以便于程序詳細了解出錯原因。出錯代碼的定義值都大于0c0000000h,因為0c0000000h開始的地址空間為系統(tǒng)使用,分配的內(nèi)存地址不可能高于這個地址,所以檢測函數(shù)執(zhí)行是否成功的時候可以使用上面的測試語句來比較返回值是否在0~0c0000000h之間。
HEAP_NO_SERIALIZE標志用來控制對私有堆的訪問是否要進行獨占性的檢測,前面曾經(jīng)提到在默認堆中申請內(nèi)存塊的操作是順序進行的,多個線程同時申請內(nèi)存的請求只有一個能馬上執(zhí)行,其他將處于等待狀態(tài),對于一個私有堆來說,這個限制仍然存在,當從堆中分配內(nèi)存時,系統(tǒng)有下面的操作步驟:
(1)遍歷已分配的和空閑的內(nèi)存塊的鏈接表。
(2)尋找一個空閑內(nèi)存塊的地址。
(3)通過將空閑內(nèi)存塊標記為“已分配”來分配新內(nèi)存塊。
(4)將新內(nèi)存塊添加給內(nèi)存塊鏈接表。
當兩個線程幾乎同時在同一個堆中申請內(nèi)存時,如果第一個線程執(zhí)行了(1)、(2)兩步的時候被系統(tǒng)切換到第二個線性,線程2同樣又執(zhí)行(1)、(2)兩步,那么它們找到的空閑內(nèi)存塊就會是同一塊內(nèi)存,結(jié)果可想而知。解決問題的辦法就是讓單個線程獨占對堆和它的鏈接表的訪問權(quán),當一個線程全部執(zhí)行了這4個步驟后才允許第二個線程開始第一個步驟。
在用默認參數(shù)建立的堆中申請內(nèi)存,系統(tǒng)會進行獨占的檢測工作,當然這要花費一定的系統(tǒng)開銷。但是當以下情況存在時,可以保證不會同時有多個線程在同一個堆中申請內(nèi)存:
● 進程只使用一個線程。
● 進程使用多個線程,但是每個線程只訪問屬于自己的私有堆。
● 進程使用多個線程,但程序中已經(jīng)有其他措施來保證它們不會同時去訪問同一個私有堆。
在這些情況下,可以指定HEAP_NO_SERIALIZE 標志來建立私有堆,這樣建立的堆不會進行獨占性的檢測,訪問速度可以更快。
參數(shù)dwInitialSize指定創(chuàng)建堆時分配給堆的物理內(nèi)存,隨著堆中內(nèi)存的分配,當這些內(nèi)存被使用完時,堆的長度可以自動擴展。dwMaximumSize參數(shù)指定了能夠擴展到的最大值,當擴展到最大值時再嘗試在堆中分配內(nèi)存的話就會失敗,這個值決定了系統(tǒng)給堆保留的連續(xù)地址空間的大小,函數(shù)會自動將這兩個參數(shù)的數(shù)值調(diào)整為頁面大小的整數(shù)倍。如果dwMaximumSize參數(shù)的值指定為0,那么堆沒有最大值限制,擴展范圍只受限于空閑的內(nèi)存總量。如果dwMaximumSize指定為非0值,在堆中申請的最大單個內(nèi)存塊不能大于7FFF8h(相當于524 KB),dwMaximumSize指定0的話就沒有這個限制。
如果一個私有堆不再需要了,可以通過調(diào)用HeapDestroy函數(shù)將它釋放:
invoke HeapDestroy,hHeap
釋放私有堆可以釋放堆中包含的所有內(nèi)存塊,也可以將堆占用的物理內(nèi)存和保留的地址空間全部返還給系統(tǒng)。如果函數(shù)運行成功,返回值是TRUE。當在進程終止的時候沒有調(diào)用HeapDestroy函數(shù)將私有堆釋放時,系統(tǒng)會自動釋放。
雖然在默認堆中的內(nèi)存申請主要使用標準內(nèi)存管理函數(shù),而堆管理函數(shù)的主要管理對象是私有堆,但是如果編程者愿意的話,也可以用堆管理函數(shù)在默認堆中分配內(nèi)存,畢竟默認堆也是一個堆,但這樣的話首先需要有一個句柄來代表默認堆,默認堆的句柄不能用HeapCreate來創(chuàng)建,但可以用GetProcessHeap函數(shù)來獲取,這個函數(shù)沒有輸入?yún)?shù),如果執(zhí)行成功則返回默認堆的句柄。注意:這個句柄是“獲取”的而不是“創(chuàng)建”的,所以不能調(diào)用HeapDestroy來釋放它,如果對它調(diào)用HeapDestroy函數(shù),系統(tǒng)會將它忽略。
2. 在堆中分配和釋放內(nèi)存塊
如果要在堆中分配內(nèi)存塊,可以使用HeapAlloc函數(shù):
invoke HeapAlloc,hHeap,dwFlags,dwBytes
.if eax && (eax < 0c0000000h)
mov lpMemory,eax
.endif
hHeap參數(shù)就是前面創(chuàng)建堆時返回的堆句柄(或者使用GetProcessHeap函數(shù)得到的默認堆句柄),用來表示在哪個堆中分配內(nèi)存,dwBytes是需要分配的內(nèi)存塊的字節(jié)數(shù),dwFlags是標志,它可以是下面值的組合:
● HEAP_NO_SERIALIZE——當使用HeapCreate時指定了HEAP_NO_SERIALIZE標志,以后這個堆中使用的所有HeapAlloc函數(shù)都不進行獨占檢測。如果使用HeapCreate時沒有指定HEAP_NO_SERIALIZE標志,可以在這里使用HEAP_NO_SERIALIZE標志單獨指定對本次分配操作不進行獨占檢測。
● HEAP_GENERATE _EXCEPTIONS——如果申請內(nèi)存失敗函數(shù)返回具體的出錯原因,而不僅返回一個NULL。同樣,當使用HeapCreate時指定了此標志的情況下,在這里就不必再一次指定。
● HEAP_ZERO_MEMORY——將分配的內(nèi)存用0初始化。
當函數(shù)分配內(nèi)存成功的時候,返回值是指向內(nèi)存塊第一個字節(jié)的指針,如果分配內(nèi)存失敗,返回值要視dwFlags的設置,如果沒有指定HEAP_GENERATE_EXCEPTIONS標志,那么返回值為NULL,否則,返回值可能是下面的數(shù)值:
● STATUS_NO_MEMORY——取值為0C0000017h,表示內(nèi)存不夠。
● STATUS_ACCESS_VIOLATION——取值為0C0000005h,表示參數(shù)不正確或者堆被破壞。
在堆中分配的內(nèi)存塊只能是固定地址的內(nèi)存塊,不像GlobalAlloc函數(shù)一樣可以分配可移動的內(nèi)存塊。如果要釋放分配到的內(nèi)存塊,可以使用HeapFree函數(shù):
invoke HeapFree,hHeap,dwFlags,lpMemory
hHeap參數(shù)是堆句柄,lpMemory是HeapAlloc函數(shù)返回的內(nèi)存塊指針,dwFlags參數(shù)中也可以使用HEAP_NO_SERIALIZE標志,含義與使用HeapAlloc時相同。當函數(shù)執(zhí)行成功的時候,返回值為非0值,執(zhí)行失敗則函數(shù)返回0。
對于用HeapAlloc分配的內(nèi)存塊,也可以使用HeapReAlloc重新調(diào)整大小:
invoke HeapReAlloc,hHeap,dwFlags,lpMemory,dwBytes
.if eax && (eax < 0c0000000h)
mov lpMemory,eax
.endif
其中dwBytes指定了新的大小,dwFlags為標志,可以組合指定的標志有:
● HEAP_GENERATE_EXCEPTIONS——參見HeapAlloc函數(shù)的說明。
● HEAP_NO_SERIALIZE——參見HeapAlloc函數(shù)的說明。
● HEAP_ZERO_MEMORY——當擴大內(nèi)存塊的時候,將新增的部分初始化為0,當縮小內(nèi)存的時候,本參數(shù)無效。
● HEAP_REALLOC_IN_PLACE_ONLY——與GlobalReAlloc函數(shù)類似,當內(nèi)存塊的高處已經(jīng)被其他內(nèi)存塊占據(jù)的時候,要擴大內(nèi)存塊必須將它移動位置,當沒有指定這個標志的時候,函數(shù)會在需要的時候自動移動內(nèi)存塊,如果指定了這個標志,則不允許內(nèi)存塊移動,這時,當內(nèi)存塊高處不是空閑的時候,函數(shù)的執(zhí)行會失敗。
如果函數(shù)執(zhí)行成功,返回值是指向新內(nèi)存塊的指針,顯而易見,當縮小或擴大內(nèi)存塊時指定了HEAP_REALLOC_IN_PLACE_ONLY標志,則這個指針必定和原來的相同,否則的話,它既有可能和原來的指針相同也有可能不同。
3. 其他堆管理函數(shù)
除了上面的一些函數(shù),堆管理函數(shù)中還有HeapLock,HeapUnlock,GetProcessHeaps,HeapCompact,HeapSize,HeapValidate和HeapWalk等函數(shù)。
GetProcessHeaps函數(shù)用來列出進程中所有的堆(注意:不要和用來獲取默認堆句柄的GetProcessHeap函數(shù)搞混),HeapWalk用來列出一個堆中所有的內(nèi)存塊,HeapValidate函數(shù)用來檢驗一個堆中所有內(nèi)存塊的有效性。這3個函數(shù)平時很少使用,一般在調(diào)試的時候使用。
GetProcessHeaps函數(shù)的用法是:
invoke GetProcessHeaps,NumberOfHeaps,lpHeaps
其中l(wèi)pHeaps是一個指針,指向用來接收堆句柄的緩沖區(qū),NumberOfHeaps參數(shù)指定了這個緩沖區(qū)中可以存放句柄的數(shù)量,顯然,緩沖區(qū)的長度應該等于NumberOfHeaps乘以4字節(jié)。函數(shù)執(zhí)行后,進程中所有堆的句柄全部返回到緩沖區(qū)中,其中也包括默認堆的句柄。
HeapWalk函數(shù)的用法是:
.repeat
invoke HeapWalk,hHeap,lpEntry
push eax
;檢測緩沖區(qū)中的內(nèi)存塊信息
pop eax
.until !eax
hHeap是需要操作的堆句柄,lpEntry指向一個包含有PROCESS_HEAP_ENTRY結(jié)構(gòu)的緩沖區(qū)。調(diào)用HeapWalk函數(shù)時,函數(shù)每次在PROCESS_HEAP_ENTRY結(jié)構(gòu)中返回一個內(nèi)存塊的信息,如果還有其他內(nèi)存塊,函數(shù)返回TRUE,程序可以一直循環(huán)調(diào)用HeapWalk函數(shù)直到函數(shù)返回FALSE為止。在多線程的程序中使用HeapWalk,必須首先使用HeapLock函數(shù)將堆鎖定,否則調(diào)用會失敗。
HeapValidate用來驗證堆的完整性或堆中某個內(nèi)存塊的完整性:
invoke HeapValidate,hHeap,dwFlags,lpMemory
其中hHeap指定要驗證的堆。如果lpMemory為NULL,那么函數(shù)順序驗證堆中所有的內(nèi)存塊;如果lpMemory指定了一個內(nèi)存塊,則只驗證這個內(nèi)存塊。dwFlags是標志,可以指定HEAP_NO_SERIALIZE 標志。如果驗證結(jié)果是所有的內(nèi)存塊都完好無損,函數(shù)返回非0值,否則函數(shù)返回0。
HeapLock函數(shù)和HeapUnlock函數(shù)用來鎖定堆和解鎖堆。這兩個函數(shù)主要用于線程的同步,當在一個線程中調(diào)用HeapLock函數(shù)時,這個線程暫時成為這個堆的所有者,也就是說只有這個線程能對堆進行操作(包括分配內(nèi)存、釋放、調(diào)用HeapWalk等函數(shù)),在別的線程中對這個堆的操作會等待在那里,直到所有者線程調(diào)用HeapUnlock解鎖為止。這兩個函數(shù)的語法如下:
invoke HeapLock,hHeap
invoke HeapUnlock,hHeap
如果函數(shù)執(zhí)行成功,返回值為非0值,否則函數(shù)返回0。一般來說,很少在程序中使用這兩個函數(shù),而總是使用HEAP_NO_SERIALIZE標志來進行同步控制,指定了這個標志的話,HeapAlloc,HeapReAlloc,HeapSize和HeapFree等函數(shù)會在內(nèi)部自己調(diào)用HeapLock和HeapUnlock函數(shù)。
HeapCompact函數(shù)用于合并堆中的空閑內(nèi)存塊并釋放不在使用中的內(nèi)存頁面:
invoke HeapCompact,hHeap,dwFlags
HeapSize函數(shù)返回堆中某個內(nèi)存塊的大小,這個大小就是使用HeapAlloc以及HeapReAlloc時指定的大小:
invoke HeapSize,hHeap,dwFlags,lpMemory
lpMemory指定了需要返回大小的內(nèi)存塊,函數(shù)的返回值是內(nèi)存塊的大小,如果執(zhí)行失敗,函數(shù)返回?1。
10.1.5 虛擬內(nèi)存管理函數(shù)
不管某個進程實際可用的物理內(nèi)存是多少,每個進程可以使用的地址空間總是2 GB,用戶程序不必考慮一個線程地址對應的物理內(nèi)存究竟安排在什么地方——是在真正的物理內(nèi)存中?在磁盤交換文件中?還是根本沒有物理內(nèi)存與之對應。
一個進程的整個地址空間是客觀存在的,但是否有內(nèi)存與該段地址空間中的地址相關(guān)聯(lián)是另外的問題,Windows負責在適當?shù)臅r間把線程地址映射到物理內(nèi)存或磁盤上的交換文件上,這就是虛擬內(nèi)存的基本概念。
在程序運行的時候,進程中每個地址都可以處于下列3種狀態(tài)的1種中:
● 占用狀態(tài)——線程地址已經(jīng)映射到實際的物理內(nèi)存中。也稱為已提交狀態(tài)。
● 自由狀態(tài)——沒有映射到物理內(nèi)存中,線程地址當前也沒有被程序使用。
● 保留狀態(tài)——雖然線程地址沒有映射到物理內(nèi)存中,但它不會被使用,直到程序希望使用它為止。
進程開始的時候,所有地址都是處于自由狀態(tài)的,這意味著它們都是自由空間并且可以被提交到物理內(nèi)存,或者為將來使用而保留起來。任何自由狀態(tài)地址在能夠被使用前,必須首先被分配為保留狀態(tài)或已提交狀態(tài)。
當使用標準內(nèi)存管理函數(shù)分配內(nèi)存的時候,用戶無法指定內(nèi)存塊位于哪個線程地址,或者不要位于哪個線程地址,而使用虛擬內(nèi)存管理函數(shù)可以做到這一點。但這樣做的理由是什么呢?考慮這樣一種情況:程序需要一個內(nèi)存塊用做緩沖區(qū),隨著程序的運行,這個內(nèi)存塊可能隨時需要擴展,最大可能擴展為100 MB大小,所以希望系統(tǒng)在分配其他內(nèi)存塊的時候不要使用這個內(nèi)存塊后面100 MB大小范圍內(nèi)的地址空間,這樣,就可以隨時將內(nèi)存塊擴大而不必移動它的位置。
除了這樣一個主要的用途外,虛擬內(nèi)存管理函數(shù)還提供轉(zhuǎn)換虛擬地址空間頁狀態(tài)的能力,一個應用程序可以把內(nèi)存的狀態(tài)從已提交改變?yōu)楸A簦虬驯Wo的模式從 PAGE _READWRITE (可讀寫)改變?yōu)?PAGE_READONLY(只讀),從而防止對某段地址空間的寫訪問;應用程序也可以鎖定一頁內(nèi)存,不讓它被交換到磁盤中。
虛擬內(nèi)存管理函數(shù)是一組名字以Virtual開頭的函數(shù),主要包括下面幾種:
● VirtualAlloc和VirtualFree——進行地址空間的分配和釋放工作。
● VirtualLock和VirtualUnlock——對內(nèi)存頁進行鎖定和解鎖。
● VirtualQuery或VirtualQueryEx——查詢內(nèi)存頁的狀態(tài)。
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -