?? 010.txt
字號:
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,\
offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
MemInfo.rc文件如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 1000
#define DLG_MAIN 100
#define IDC_INFO 101
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 188, 193, 140, 75
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "內存狀態"
FONT 9, "宋體"
{
LTEXT "", IDC_INFO, 6, 6, 135, 65
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
程序設置了一個定時器,以周期為1秒來刷新當前內存的使用情況,在WM_TIMER消息中用GlobalMemoryStatus獲取內存狀態并用wsprintf將數據轉換成字符串,然后顯示在對話框的IDC_INFO文本框中。
在筆者的計算機中,程序運行的結果如圖10.2所示,計算機的物理內存配置為128 MB,這個數值和物理內存總數(dwTotalPhys字段)符合,dwMemoryLoad字段顯示的72%等于空閑物理內存(dwAvailPhys字段)除以物理內存總數,計算機上當前虛擬內存交換文件大小為192 MB,小于最大限制dwTotalPageFile字段。
在與地址空間相關的數值上,dwTotalVirtual字段的顯示結果是2 147 352 576,等于2 GB減去128 KB,這是因為這2 GB的最低端的和最高端的兩個64 KB是系統保留的(00000000h~0000ffffh,7fff0000h~7fffffffh)。
圖10.2 Meminfo程序的運行結果
Windows可以根據內存使用的需求自動調整交換文件的大小,比如Meminfo程序運行顯示的當前磁盤交換文件可用空間(dwAvailPageFile字段)為168 MB,但是如果嘗試申請一個高達300 MB的內存塊,會發現仍然可以申請成功,這時dwTotalPageFile字段的大小會自動增長到500多MB,把內存塊釋放掉的話,dwTotalPageFile字段會恢復到原來的數值。虛擬內存的使用給我們帶來了很多的方便,我們可以使用超過物理內存好幾倍的內存空間,但是如果所需的內存大大高于物理內存的大小,那么申請內存還是會失敗,因為這會引起物理內存和交換文件之間的數據頻繁交換,大量的磁盤請求將使系統性能降低到沒有實際使用的意義,讀者可以嘗試在128 MB物理內存的計算機上申請一個1 GB的內存塊,即使擁有遠遠大于1 GB的磁盤剩余空間供交換文件使用,也是不會成功的!如果讀者需要使用大大高于物理內存的內存空間,可以嘗試自己進行磁盤交換工作。
10.1.3 標準內存管理函數
標準內存管理函數的功能是在進程的默認堆中申請和釋放內存塊,它由下面一些函數組成:GlobalAlloc,GlobalFree和GlobalReAlloc分別用來申請、釋放和修改內存大小;GlobalLock和GlobalUnlock用來進行鎖定操作;而GlobalDiscard,GlobalFlags,GlobalHandle和GlobalSize等用來丟棄內存或獲取已分配內存的一些信息。
在Win16中,內存管理函數有“全局”或“本地”之分,它們的區別在于返回的指針是遠指針還是近指針,全局內存管理函數名是以“Global”開頭的,而“本地”內存管理函數名是以“Local”開頭的。在Win32中,指針并沒有遠近之分,只有一種32位的指針,但為了保持向下兼容,這些函數名仍然沿用了下來,上面列出的這些函數名都是以“Global”開頭的,同樣,Win32中也存在以“Local”開頭的內存管理函數名,只要這些函數名中的“Global”全部換成“Local”就可以了。這兩組函數在Win32中是完全相同的,讀者可以自由使用名字以Global或Local為前綴的函數。
用標準內存管理函數可以分配的內存有兩種:固定地址的內存塊和可移動的內存塊,而可移動的內存塊又可以進一步定義為可丟棄的,讓我們逐步來討論它們的不同。
1. 固定的內存塊
常規意義上的內存就是固定的內存塊,因為申請到內存后,這塊內存的線性地址是固定不變的。要申請一塊固定的內存,可以使用函數:
invoke GlobalAlloc,GMEM_FIXED or GMEM_ZEROINIT,dwBytes
.if eax
mov lpMemory,eax
.endif
第一個參數是標志,GMEM_FIXED表示申請的是固定的內存塊,GMEM_ZEROINIT表示需要將內存塊中的所有字節預先初始化為0,也可以簡單地使用GPTR標志,它就相當于是GMEM_FIXED or GMEM_ZEROINIT;第2個參數dwBytes指出了需要申請的是以字節為單位的內存大小。如果內存申請失敗,eax中返回NULL,否則返回值是一個指向內存塊起始地址的指針,用戶需要保存這個指針,在使用內存或者釋放內存的時候還要用到它。
如果要釋放一個先前申請的固定內存塊,可以使用GlobalFree函數:
invoke GlobalFree,lpMemory
如果釋放成功,函數返回NULL,否則函數返回的值就是輸入的lpMemory。程序在不再使用內存塊的時候應該使用這個函數將內存釋放,即使程序在退出的時候忘記了釋放內存,Windows也會自動將它們釋放。
在實際使用中往往需要改變一個內存塊的大小,這時候就要用到GlobalReAlloc函數,這個函數可以縮小或者擴大一塊已經申請到的內存:
invoke GlobalReAlloc,lpMemory,dwBytes,uFlags
.if e ax
mov lpNewMemory,eax
.endif
lpMemory是先前申請的內存塊指針,dwBytes是新的大小,如果這個數值比原來申請的時候要小,也就是需要縮小內存塊,那么uFlags標志參數可以是NULL,如果縮小內存塊的操作不成功,那么函數的返回值為0,否則是新的縮小了的內存塊指針,當然,這個指針和原來的指針肯定是一樣的。
但是需要擴大一個內存塊的時候,情況就稍微有些復雜了。讓我們做一個實驗來模擬這樣一種情況:首先申請兩個1000h大小的固定內存塊,得到兩個指針,讀者可以發現第二塊幾乎緊接第一塊內存,一般情況下如果第一塊內存的地址是X,那么第二塊內存的地址幾乎就是X+1000h,如果需要將第一個內存塊擴大到2000h字節,那么只能在別的地方開辟一個2000h大小的內存塊,因為原來位置后面的1000h已經被第二塊內存占用了,這就意味著新的指針可能和原來的不一樣。
可以在GlobalReAlloc函數中通過指定不同的uFlags來規定是否允許Windows在必要的時候移動內存塊。當uFlags中有GMEM_MOVEABLE選項的時候,如果需要移動內存塊,Windows會在別的地方開辟一塊新的內存,并把原來內存塊中的內容自動復制到新的內存塊中,這時函數的返回值是新的指針,原來的指針作廢。
如果不指定GMEM_MOVEABLE選項,那么只有當內存塊后面擴展所需的空間沒有被使用時,函數才會執行成功,否則,函數失敗并返回NULL,這時原來的指針繼續有效。
為了保證內存塊擴大成功,建議總是使用下面的語句來擴大和縮小內存:
invoke GlobalReAlloc,lpMemory,dwBytes,GMEM_ZEROINIT or GMEM_MOVEABLE
.if eax
mov lpMemory,eax
.endif
指定GMEM_ZEROINIT選項可以使內存塊擴大的部分自動被初始化為0,然后程序判斷返回值,如果改變大小成功的話,則用新的指針替換原來的指針,其他和原來指針有關的值也不要忘了同時更新。
2. 可移動的內存塊
可移動的內存塊在不使用的時候允許Windows改變它的線性地址,為什么要使用可移動的內存塊呢?惟一的理由是防止內存的碎片化,這里的碎片化指的是用戶程序自己地址空間的碎片化,而不是指整個操作系統。讀者可能有個疑問:與DOS操作系統相比,Win32用戶程序可用的地址空間要大得多,整整2 GB的地址空間難道還怕用完嗎?讓我們先用一個例子來演示一下,并由此引伸出可移動內存塊的使用方法。
在這個例子中,讓我們來設計一個“陰謀”,用一個極端的方法“謀殺”掉所有的地址空間:程序首先申請一個1 MB大小的固定內存塊,然后繼續申請內存并把前面申請的內存塊大小改為100 B,由此循環,因為縮小內存塊釋放出來的空間大小為999 900 B,新申請的內存塊無法使用這些地址空間,只能繼續使用后面大塊的地址空間,如果沒有算錯的話,經過2 000次左右的循環就會把全部的地址空間分割成2 000個999 900 B大小的空間(2GB等于2 000個1 MB),到時候雖然只保留了近200 KB大小的內存(2 000個100 B),但是這2 000個100 B均勻分布在2 GB的地址空間內,以至于接下來任何大于999 900 B的內存申請操作都無法成功。
這個程序位于所附光盤的Chapter10\Fragment目錄內,Fragment.asm的源代碼如下:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定義
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定義
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 100
IDC_MEMORY equ 101
IDC_COUNT equ 102
IDC_INFO equ 103
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 數據段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
dwTotalMemory dd ?
dwCount dd ?
ifCanQuit dd ?
.const
szInfo db ~無法繼續申請 1MB 大小的內存!~,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcThread proc uses ebx ecx edx esi edi,lParam
local @lpLastMem
invoke GlobalAlloc,GPTR,1000000
mov @lpLastMem,eax
inc dwCount
add dwTotalMemory,1000000
.repeat
push @lpLastMem
invoke GlobalAlloc,GPTR,1000000
mov @lpLastMem,eax
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -