win32汇编-内存管理 (五)
当设计一个可能需要申请大量内存的程序时,如何预先得知系统的配置情况呢?对此可以使用GlobalMemoryStatus函数:
invoke GlobalMemoryStatus,lpBuffer
lpBuffer指向一个MEMORYSTATUS结构,结构的定义如下:
MEMORYSTATUS STRUCT
dwLength DWORD ? ;本结构的长度
dwMemoryLoad DWORD ? ;已用内存的百分比
dwTotalPhys DWORD ? ;物理内存总量
dwAvailPhys DWORD ? ;可用物理内存
dwTotalPageFile DWORD ? ;交换文件总的大小
dwAvailPageFile DWORD ? ;交换文件中空闲部分大小
dwTotalVirtual DWORD ? ;用户可用的地址空间
dwAvailVirtual DWORD ? ;当前空闲的地址空间
MEMORYSTATUS ENDS
在调用之前需要首先将dwLength字段设置为MEMORYSTATUS结构的长度,当调用GlobalMemoryStatus函数后,函数会在结构中返回对应的数值。注意:dwTotalPageFile字段返回的是交换文件的最大值,并不是当前实际建立的交换文件的大小,一般当前的交换文件大小会小于这个数值,但这个数值的大小也不是确定的,如果需要的话,系统会增加它的大小直到不再有空余的磁盘空间放置交换文件为止。
标准内存管理函数的功能是在进程的默认堆中申请和释放内存块,它由下面一些函数组成:
GlobalAlloc,GlobalFree和GlobalReAlloc分别用来申请、释放和修改内存大小;
GlobalLock和GlobalUnlock用来进行锁定操作;
在Win32中是完全相同的,读者可以自由使用名字以Global或Local为前缀的函数。
而GlobalDiscard,GlobalFlags,GlobalHandle和GlobalSize等用来丢弃内存或获取已分配内存的一些信息。
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
在实际使用中往往需要改变一个内存块的大小,这时候就要用到GlobalReAlloc函数,这个函数可以缩小或者扩大一块已经申请到的内存:
invoke GlobalReAlloc,lpMemory,dwBytes,uFlags
.if e ax
mov lpNewMemory,eax
.endif
lpMemory是先前申请的内存块指针,dwBytes是新的大小,如果这个数值比原来申请的时候要小,也就是需要缩小内存块,那么uFlags标志参数可以是NULL,如果缩小内存块的操作不成功,那么函数的返回值为0,否则是新的缩小了的内存块指针,当然,这个指针和原来的指针肯定是一样的。
可以在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改变它的线性地址
要申请一个可移动的内存块,使用的函数还是GlobalAlloc,但需要使用不同的参数:
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,dwBytes
.if eax
mov hMemory,eax
.endif
GMEM_MOVEABLE标志指定了分配的内存是可移动的,GMEM_ZEROINIT同样表示将申请到的内存块的内容初始化为0(也可以用GHND标志,它就相当于GMEM _MOVEABLE or GMEM_ZEROINIT);如果内存申请失败,eax中返回NULL,成功的话返回值是一个句柄而不是内存指针,用户需要保存这个句柄,在锁定或释放内存的时候还要用到它。一个进程可以申请的可移动内存的块数最大不能超过65 536个,申请固定内存块时则没有数量限制。
要使用可移动内存之前,需要把它锁定,这相当于告诉Windows现在程序要使用这块内存了,不能将它移动,锁定内存使用GlobalLock函数:
invoke GlobalLock,hMemory
.if eax
mov lpMemory,eax
.endif
函数的入口参数是GlobalAlloc返回的内存句柄,如果锁定成功,函数返回一个指针,程序可以用使用固定内存块同样的方法来使用它;如果锁定失败,则函数返回NULL。每次锁定返回的指针位置可能是不同的,但内存块中的数据不会变化。
当程序暂时不需要操作这块内存的时候,应该将它解锁,否则和使用固定的内存块就没有区别了,解锁使用GlobalUnlock函数:
invoke GlobalUnlock,hMemory
函数的参数同样是GlobalAlloc返回的句柄,解锁成功的话函数返回非0值。读者可能有个问题:在多线程的程序中,两个地方同时锁定内存,但当一个地方还在使用的情况下另一个地方却调用GlobalUnlock将内存解锁了怎么办?其实不用担心这个问题,Windows为每个可移动的内存句柄维护一个锁定计数,每次锁定内存的时候计数加1,解锁的时候计数减1,只有当计数为0的时候内存才真正被解锁,所以只要程序中的GlobalLock函数和GlobalUnlock函数是配对的,就不用担心这个问题。
要释放一个可移动的内存块,同样使用GlobalFree函数:
invoke GlobalFree,hMemory
但使用的参数是GlobalAlloc返回的内存句柄,如果释放成功,函数返回NULL。不管内存当前是否处在锁定状态,都可以被成功释放。
调整可移动内存块的大小,同样使用GlobalReAlloc函数:
invoke GlobalReAlloc,hMemory,dwBytes,GMEM_ZEROINIT or GMEM_MOVEABLE
如果调整成功,返回值就是输入的hMemory,失败的话返回值是NULL。即使内存块在锁定状态,函数仍然可以调用成功,但这时候内存块可能已经被移动了位置,原来用GlobalLock函数获取的指针可能已经失效了,所以调整可移动内存块的大小最好还是先将内存解锁,等调整完毕以后再锁定使用。
3. 可丢弃的内存块
分配可移动内存块的时候还可以配合GMEM_MOVEABLE标志使用GMEM_DI SCARDABLE标志,这样生成的内存块是可丢弃的内存块,表示当Windows急需内存使用的时候,可以将它从物理内存中丢弃,可丢弃的内存块首先必须是可移动的内存块。函数调用如下:
invoke GlobalAlloc,GHND or GMEM_DISCARDABLE,dwBytes
.if eax
mov hMemory,eax
.endif
当用GlobalLock锁定内存的时候如果返回NULL指针,表示内存已经被Windows丢弃了,当然其中的数据也丢失了,程序需要重新生成数据。当内存块被丢弃的时候,内存句柄还是有效的,如果程序还要使用这个句柄,那么可以对它使用GlobalReAlloc函数来重新分配内存。
当可丢弃内存块的锁定计数为0时,程序也可以使用GlobalDiscard函数主动将它丢弃,这和Windows将它丢弃的效果是一样的:
invoke GlobalDiscard,hMemory
4. 获取内存块的信息
标准内存管理函数中的其他函数GlobalFlags,GlobalHandle和GlobalSize用来获取已分配内存块的一些信息。
GlobalFlags函数主要用来获取可移动内存块当前的锁定计数,也可以用来检测可丢弃内存块是否已经被丢弃。对一个hMemory调用GlobalFlags函数如下所示:
invoke GlobalFlags,hMemory
如果不是返回GMEM_INVALID_HANDLE,则表示调用成功,这时返回值的低8位是内存块的锁定计数,程序可以用GMEM_LOCKCOUNT对获取计数值进行and操作(在Windows.inc头文件中,GMEM_LOCKCOUNT定义为0ffh):
invoke GlobalFlags,hMemory
and eax,GMEM_LOCKCOUNT
mov dwLockCount,eax
返回值的其他数据位可能包含下列标志:
● GMEM_DISCARDABLE 表示内存块是可丢弃内存块。
● GMEM_DISCARDED 表示内存块已经被丢弃。
GlobalHandle可以从GlobalLock函数得到的lpMemory值获取其对应的hMemory,而GlobalSize函数可以获知一个内存块的尺寸。