WIN32汇编语言教程:第10章 内存管理和文件操作 · 10.1 内存管理(2)
.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_INFO equ 101 ;#################################################################### ; 数据段 ;#################################################################### .data? hInstance dd ? hWinMain dd ? .const szInfo db '物理内存总数 %lu 字节',0dh,0ah db '空闲物理内存 %lu 字节',0dh,0ah db '虚拟内存总数 %lu 字节',0dh,0ah db '空闲虚拟内存 %lu 字节',0dh,0ah db '已用内存比例 %d%%',0dh,0ah db '————————————————',0dh,0ah db '用户地址空间总数 %lu 字节',0dh,0ah db '用户可用地址空间 %lu 字节',0dh,0ah,0 ;#################################################################### ; 代码段 ;#################################################################### .code ;#################################################################### _GetMemInfo proc local @stMemInfo:MEMORYSTATUS local @szBuffer[1024]:byte mov @stMemInfo.dwLength,sizeof @stMemInfo invoke GlobalMemoryStatus,addr @stMemInfo invoke wsprintf,addr @szBuffer,addr szInfo,\ @stMemInfo.dwTotalPhys,@stMemInfo.dwAvailPhys,\ @stMemInfo.dwTotalPageFile,\ @stMemInfo.dwAvailPageFile,\ @stMemInfo.dwMemoryLoad,\ @stMemInfo.dwTotalVirtual,@stMemInfo.dwAvailVirtual invoke SetDlgItemText,hWinMain,IDC_INFO,addr @szBuffer ret _GetMemInfo endp ;#################################################################### _ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam mov eax,wMsg .if eax == WM_TIMER call _GetMemInfo .elseif eax == WM_CLOSE invoke KillTimer,hWnd,1 invoke EndDialog,hWnd,NULL ;******************************************************************** .elseif eax == WM_INITDIALOG push hWnd pop hWinMain invoke LoadIcon,hInstance,ICO_MAIN invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax invoke SetTimer,hWnd,1,1000,NULL call _GetMemInfo ;******************************************************************** .else mov eax,FALSE ret .endif mov eax,TRUE 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 <resource.h> //################################################################## #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已经被第二块内存占用了,这就意味着新的指针可能和原来的不一样。
上页:第10章 内存管理和文件操作 · 10.1 内存管理(1) 下页:第10章 内存管理和文件操作 · 10.1 内存管理(3)