内存管理2 - Win32汇编语言055
内存管理2
让编程改变世界
Change the world by program
标准内存管理函数
标准内存管理函数的功能是在进程的默认堆中申请和释放内存块,它由下面一些函数组成: GlobalAlloc,GlobalFree 和 GlobalReAlloc 分别用来申请、释放和修改内存大小。 GlobalLock 和 GlobalUnlock 用来进行锁定操作。 GlobalDiscard,GlobalFlags,GlobalHandle 和 GlobalSize 等用来丢弃内存或获取已分配内存的一些信息。友情提示:
在 Win16 中,内存管理函数有"全局"或"本地"之分,它们的区别在于返回的指针是远指针还是近指针,全局内存管理函数名是以"Global"开头的,而"本地"内存管理函数名是以"Local"开头的。 在 Win32 中,指针并没有远近之分,只有一种32位的指针,但为了保持向下兼容,这些函数名仍然沿用了下来。 这两组函数在 Win32 中是完全相同的,读者可以自由使用名字以 Global 或 Local 为前缀的函数。用标准内存管理函数可以分配的内存有两种:
固定地址的内存块和可移动的内存块 可移动的内存块又可以进一步定义为可丢弃的,让我们逐步来讨论它们的不同。固定的内存块
常规意义上的内存就是固定的内存块,因为申请到内存后,这块内存的线性地址是固定不变的。 要申请一块固定的内存,可以使用函数:invoke GlobalAlloc,GMEM_FIXED or GMEM_ZEROINIT,dwBytes
解释一下: 第一个参数是标志,GMEM_FIXED 表示申请的是固定的内存块,GMEM_ZEROINIT 表示需要将内存块中的所有字节预先初始化为0,也可以简单地使用 GPTR 标志,它就相当于是GMEM_FIXED or GMEM_ZEROINIT。 第二个参数 dwBytes 指出了需要申请的是以字节为单位的内存大小。如果内存申请失败,eax 中返回 NULL,否则返回值是一个指向内存块起始地址的指针,用户需要保存这个指针,在使用内存或者释放内存的时候还要用到它。 如果要释放一个先前申请的固定内存块,可以使用 GlobalFree 函数:invoke GlobalFree, lpMemory
如果释放成功,函数返回 NULL,否则函数返回的值就是输入的 lpMemory。 另外程序在不再使用内存块的时候应该使用这个函数将内存释放,不过就算程序在退出的时候忘记了释放内存,Windows 也会自动将它们释放。 在实际使用中往往需要改变一个内存块的大小,这时候就要用到 GlobalReAlloc 函数。 该函数可以缩小或扩大一块已经申请到的内存: [codesyntax lang="asm"]invoke GlobalReAlloc,lpMemory,dwBytes,uFlags .if eax mov lpNewMemory,eax .endif[/codesyntax] lpMemory 是先前申请的内存块指针,dwBytes 是新的大小,如果这个数值比原来申请的时候要小。 也就是需要缩小内存块,那么 uFlags 标志参数可以是 NULL。 如果缩小内存块的操作不成功,那么函数的返回值为0,否则是新的缩小了的内存块指针,当然,这个指针和原来的指针肯定是一样的。 但是需要扩大一个内存块的时候,情况就稍微有些复杂了。
我们来考虑这样一种情况:
首先申请两个 1000h 大小的固定内存块,得到两个指针,可以发现第二块几乎紧接第一块内存的。(请自行尝试) 一般情况下如果第一块内存的地址是 X,那么第二块内存的地址几乎就是 X + 1000h,如果需要将第一个内存块扩大到 2000h 字节,那么只能在别的地方开辟一个 2000h 大小的内存块,因为原来位置后面的 1000h 已经被第二块内存占用了,这就意味着新的指针可能和原来的不一样。 可以在 GlobalReAlloc 函数中通过指定不同的 uFlags 来规定是否允许 Windows 在必要的时候移动内存块。 当 uFlags 中有 GMEM_MOVEABLE 选项的时候,如果需要移动内存块,Windows 会在别的地方开辟一块新的内存,并把原来内存块中的内容自动复制到新的内存块中,这时函数的返回值是新的指针,原来的指针作废。 如果不指定 GMEM_MOVEABLE 选项,那么只有当内存块后面扩展所需的空间没有被使用时,函数才会执行成功,否则,函数失败并返回 NULL,这时原来的指针继续有效。 为了保证内存块扩大成功,建议总是使用下面的语句来扩大和缩小内存: [codesyntax lang="asm"]invoke GlobalReAlloc, lpMemory, dwBytes, GMEM_ZEROINIT or GMEM_MOVEABLE .if eax mov lpMemory,eax .endif[/codesyntax] 指定 GMEM_ZEROINIT 选项可以使内存块扩大的部分自动被初始化为0,然后程序判断返回值。 如果改变大小成功的话,则用新的指针替换原来的指针,其他和原来指针有关的值也不要忘了同时更新。 接下来,我们来讨论:可移动的内存块
可移动的内存块
可移动的内存块在不使用的时候允许 Windows 改变它的线性地址,为什么要使用可移动的内存块呢? 惟一的理由是防止内存的碎片化,当进程长时间频繁地申请和释放不同大小的内存块后,申请的大量小块内存可能零散地分布在整个地址空间。 尽管空闲的内存总数不小,但久而久之却没有剩下连续的大块空闲地址,最终导致无法再申请大块的内存。 解决内存碎片化的办法很简单,因为碎片之间有大量的内存是空闲的,只要允许 Windows 移动小块的在用内存,就可以将碎片合并成大块的空闲内存。 但是在用内存被移动后,程序中对应的指针也要随着改变,不然就会造成访问地址错误。 另外,在使用内存的过程中,内存需要有个锁定的过程,不然用到一半就被 Windows 移动了,结果依然是错误的。 只有程序将内存解锁,Windows 才可以自由移动它们,这就引申出可移动内存块的概念和操作的基本方法。 要申请一个可移动的内存块,使用的函数还是GlobalAlloc,但需要使用不同的参数: [codesyntax lang="asm"]invoke GlobalAlloc, GMEM_MOVEABLE or GMEM_ZEROINIT, dwBytes .if eax mov hMemory, eax .endif[/codesyntax] GMEM_MOVEABLE 标志指定了分配的内存是可移动的,GMEM_ZEROINIT 同样表示将申请到的内存块的内容初始化为0(也可以用 GHND 标志,它就相当于 GMEM _MOVEABLE or GMEM_ZEROINIT)。 如果内存申请失败,eax 中返回 NULL,成功的话返回值是一个句柄而不是内存指针,用户需要保存这个句柄,在锁定或释放内存的时候还要用到它。 一个进程可以申请的可移动内存的块数最大不能超过 65536 个,申请固定内存块时则没有数量限制。 要使用可移动内存之前,需要把它锁定,这相当于告诉 Windows 现在程序要使用这块内存了,不能将它移动,锁定内存使用 GlobalLock 函数: [codesyntax lang="asm"]
invoke GlobalLock, hMemory .if eax mov lpMemory, eax .endif[/codesyntax] 函数的入口参数是 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 函数获取的指针可能已经失效了。 所以调整可移动内存块的大小最好还是先将内存解锁,等调整完毕以后再锁定使用。 由于使用可移动的内存块多了一个锁定的动作,速度自然要比使用固定的内存块要慢一点,但固定内存块又存在碎片问题,程序中使用哪种方法有个取舍的问题。 如果程序要频繁地分配和释放不定长的内存块,内存的碎片化现象就比较严重,特别是当程序长时间运行时,这种情况下使用可移动内存块比较好。 如果程序只进行少量的内存操作,或者虽然频繁分配和释放内存,但使用的内存块长度都是一样的,则使用固定内存块可以节省时间。 [buy] 获得所有教学视频、课件、源代码等资源打包 [/buy] [Downlink href='http://kuai.xunlei.com/d/BdsUAwrRHwBnT7JS58b']视频下载[/Downlink]