八喜电子书 > 经管其他电子书 > windows环境下32位汇编语言程序设计 >

第64部分

windows环境下32位汇编语言程序设计-第64部分

小说: windows环境下32位汇编语言程序设计 字数: 每页4000字

按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
————未阅读完?加入书签已便下次继续阅读!



浔皇悄诖嬷刚耄没枰4嬲飧鼍浔谒ɑ蚴头拍诖娴氖焙蚧挂玫剿R桓鼋炭梢陨昵氲目梢贫诖娴目槭畲蟛荒艹�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

 使用内存函数时有两个地方需要特别注意:

(1)NULL指针的检测——GlobalAlloc函数和GlobalLock函数都可以返回内存指针,在使用指针前一定要检测它的有效性,如果使用了函数执行失败而返回的NULL指针来访问数据,会导致程序越权访问不该访问的地方,从而被Windows毫不留情地终止掉,这就是例子代码中总是有个if语句来判断eax是否为NULL的原因。

(2)注意访问越界问题——越界操作也会引起越权访问,千万不要到超出内存块长度的地方去访问,例如,使用lstrcpy之类的函数处理字符串之前,先用lstrlen检测字符串长度是一个好习惯。

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函数可以获知一个内存块的尺寸。



 
来源:电子工业出版社 作者:罗云彬 上一页         回书目         下一页          
上一页         回书目         下一页          
  


第10章 内存管理和文件操作


10。1 内 存 管 理(5)

    
10。1。4  堆管理函数

Windows的“堆”分为默认堆和私有堆两种。默认堆是在程序初始化时由操作系统自动创建的,所有的标准内存管理函数都是在默认堆中申请内存的;而私有堆相当于在默认堆中保留了一大块内存,用堆管理函数可以在这个保留的内存块中分配内存。

一个进程的默认堆只有一个,而私有堆可以被创建多个。使用私有堆的缺点是分配和释放内存块的过程中多了一个扫描堆中的内存链的过程,所以单从分配内存的角度来讲,在私有堆中分配内存速度似乎要慢一点。

但实际上,有些时候使用私有堆可能更有好处。

首先,可以使用默认堆的函数有多种,而它们可能在不同的线程中同时对默认堆进行操作,为了保持同步,对默认堆的访问是顺序进行的,也就是说,在同一时间内每次只有一个线程能够分配和释放默认堆中的内存块。如果两个线程试图同时分配默认堆中的内存块,那么只有一个线程能够进行,另一个线程必须等待第一个线程的内存块分配结束之后才能继续执行。而私有堆的空间是预留的,不同线程在不同的私有堆中同时分配内存并不会引起冲突,所以整体的运行速度可能更快。

其次,当系统必须在物理内存和页文件之间进行页面交换的时候,系统的性能会受到很大的影响,在某些情况下,使用私有堆可以防止系统频繁地在物理内存和交换文件之间进行数据交换,因为将经常访问的内存局限于一个小范围地址的话,页面交换就不太可能发生,把频繁访问的大量小块内存放在同一个私有堆中就可以保证它们在内存中的位置接近。

再则,使用私有堆也有利于封装和保护模块化的程序。当程序包含多个模块的时候,如果使用标准内存管理函数在默认堆中分配内存,那么所有模块分配的内存块是交叉排列在一起的,如果模块A中的一个错误导致内存操作越界,可能会覆盖掉模块B使用的内存块,到模块B执行的时候出错了,我们却很难发现错误的源头来自于模块A。如果让不同的模块使用自己的私有堆,那么它们使用的内存就会完全隔离开来,虽然越界错误仍然可能发生,但很容易跟踪和定位。

最后,使用私有堆也使大量内存的清理变得方便,在默认堆中分配的内存需要一块块单独释放,但将一个私有堆释放后,在这个堆里的内存就全部被释放掉了,并不需要预先释放堆中的每个内存块,这样非常便于模块的扫尾工作。

1。 私有堆的创建和释放

创建私有堆的函数是HeapCreate:

    invoke  HeapCreate,flOptions,dwInitialSize,dwMaximumSize

    。if     eax && (eax 《 0c0000000h)

            mov hHeap,eax

    。endif

flOptions参数是标志,用来指定堆的属性,可以指定的属性有HEAP_NO_SERIALIZE和HEAP_GENERATE_EXCEPTIONS两种。

HEAP_GENERATE_EXCEPTIONS标志用来指定函数失败时的返回值,不指定这个标志的话,函数失败时返回NULL,否则返回一个具体的出错代码,以便于程序详细了解出错原因。出错代码的定义值都大于0c0000000h,因为0c0000000h开始的地址空间为系统使用,分配的内存地址不可能高于这个地址,所以检测函数执行是否成功的时候可以使用上面的测试语句来比较返回值是否在0~0c0000000h之间。

HEAP_NO_SERIALIZE标志用来控制对私有堆的访问是否要进行独占性的检测,前面曾经提到在默认堆中申请内存块的操作是顺序进行的,多个线程同时申请内存的请求只有一个能马上执行,其他将处于等待状态,对于一个私有堆来说,这个限制仍然存在,当从堆中分配内存时,系统有下面的操作步骤:

(1)遍历已分配的和空闲的内存块的链接表。

(2)寻找一个空闲内存块的地址。

(3)通过将空闲内存块标记为“已分配”来分配新内存块。

(4)将新内存块添加给内存块链接表。

当两个线程几乎同时在同一个堆中申请内存时,如果第一个线程执行了(1)、(2)两步的时候被系统切换到第二个线性,线程2同样又执行(1)、(2)两步,那么它们找到的空闲内存块就会是同一块内存,结果可想而知。解决问题的办法就是让单个线程独占对堆和它的链接表的访问权,当一个线程全部执行了这4个步骤后才允许第二个线程开始第一个步骤。

在用默认参数建立的堆中申请内存,系统会进行独占的检测工作,当然这要花费一定的系统开销。但是当以下情况存在时,可以保证不会同时有多个线程在同一个堆中申请内存:

●   进程只使用一个线程。

●   进程使用多个线程,但是每个线程只访问属于自己的私有堆。

●   进程使用多个线程,但程序中已经有其他措施来保证它们不会同时去访问同一个私有堆。

在这些情况下,可以指定HEAP_NO_SERIALIZE    标志来建立私有堆,这样建立的堆不会进行独占性的检测,访问速度可以更快。

参数dwInitialSize指定创建堆时分配给堆的物理内存,随着堆中内存的分配,当这些内存被使用完时,堆的长度可以自动扩展。dwMaximumSize参数指定了能够扩展到的最大值,当扩展到最大值时再尝试在堆中分配内存的话就会失败,这个值决定了系统给堆保留的连续地址空间的大小,函数会自动将这两个参数的数值调整为页面大小的整数倍。如果dwMaximumSize参数的值指定为0,那么堆没有最大值限制,扩展范围只受限于空闲的内存总量。如果dwMaximumSize指定为非0值,在堆中申请的最大单个内存块不能大于7FFF8h(相当于524 KB),dwMaximumSize指定0的话就没有这个限制。

如果一个私有堆不再需要了,可以通过调用HeapDestroy函数将它释放:

    invoke  HeapDestroy,hHeap

释放私有堆可以释放堆中包含的所有内存块,也可以将堆占用的物理内存和保留的地址空间全部返还给系统。如果函数运行成功,返回值是TRUE。当在进程终止的时候没有调用HeapDestroy函数将私有堆释放时,系统会自动释放。

虽然在默认堆中的内存申请主要使用标准内存管理函数,而堆管理函数的主要管理对象是私有堆,但是如果编程者愿意的话,也可以用堆管理函数在默认堆中分配内存,毕竟默认堆也是一个堆,但这样的话首先需要有一个句柄来代表默认堆,默认堆的句柄不能用HeapCreate来创建,但可以用GetProcessHeap函数来获取,这个函数没有输入参数,如果执行成功则返回默认堆的句柄。注意:这个句柄是“获取”的而不是“创建”的,所以不能调用HeapDestroy来释放它,如果对它调用HeapDestroy函数,系统会将它忽略。

2。 在堆中分配和释放内存块

如果要在堆中分配内存块,可以使用HeapAlloc函数:

    invoke  HeapAlloc,hHeap,dwFlags,dwBytes

    。if     eax && (eax 《 0c0000000h)

            mov     lpMemory,eax

    。endif

hHeap参数就是前面创建堆时返回的堆句柄(或者使用GetProcessHeap函数得到的默认堆句柄),用来表示在哪个堆中分配内存,dwBytes是需要分配的内存块的字节数,dwFlags是标志,它可以是下面值的组合:

●   HEAP_NO_SERIALIZE——当使用HeapCreate时指定了HEAP_NO_SERIALIZE标志,以后这个堆中使用的所有HeapAlloc函数都不进行独占检测。如果使用HeapCreate时没有指定HEAP_NO_SERIALIZE标志,可以在这里使用HEAP_NO_SERIALIZE标志单独指定对本次分配操作不进行独占检测。

●   HEAP_GENERATE _EXCEPTIONS——如果申请内存失败函数返回具体的出错原因,而不仅返回一个NULL。同样,当使用HeapCreate时指定了�

返回目录 上一页 下一页 回到顶部 赞(1) 踩(1)

你可能喜欢的