现在的位置: 首页 > 综合 > 正文

Windows 内存管理

2018年06月06日 ⁄ 综合 ⁄ 共 7109字 ⁄ 字号 评论关闭

6.1  Windows内存管理

      
内存管理对于编写出高效率的Windows程序是非常重要的,这是因为Windows是多任务系统,它的内存管理和单任务的DOS相比有很大的差异。DOS是单任务操作系统,应用程序分配到内存后,如果它不主动释放,系统是不会对它作任何改变的;但Windows却不然,它在同一时刻可能有多个应用程序共享内存,有时为了使某个任务更好地执行,Windows系统可能会对其它任务分配的内存进行移动,甚至删除。因此,我们在Windows应用程序中使用内存时,要遵循Windows内存管理的一些约定,以尽量提高Windows内存的利用率。

6.1.1  Windows内存对象

      
Windows
应用程序可以申请分配属于自己的内存块,内存块是应用程序操作内存的单位,它也称作内存对象,在Windows中通过内存句柄来操作内存对象。内存对象根据分配的范围可分为全局内存对象和局部内存对象;根据性质可分为固定内存对象,可移动内存对象和可删除内存对象。

      
固定内存对象,特别是局部固定内存对象和DOS的内存块很类似,它一旦分配,就不会被移动或删除,除非应用程序主动释放它。并且对于局部固定内存对象来说,它的内存句柄本身就是内存对象的16位近地址,可供应用程序直接存取,而不必象其它类型的内存对象那样要通过锁定在内存某固定地址后才能使用。

      
可移动内存对象没有固定的地址,Windows系统可以随时把它们移到一个新地址。内存对象的可移动使得Windows能有效地利用自由内存。例如,如果一个可移动的内存对象分开了两个自由内存对象,Windows可以把可移动内存对象移走,将两个自由内存对象合并为一个大的自由内存对象,实现内存的合并与碎片回收。

      
可删除内存对象与可移动内存对象很相似,它可以被Windows移动,并且当Windows需要大的内存空间满足新的任务时,它可以将可删除内存对象的长度置为0,丢弃内存对象中的数据。

      
可移动内存对象和可删除内存对象在存取前必须使用内存加锁函数将其锁定,锁定了的内存对象不能被移动和删除。因此,应用程序在使用完内存对象后要尽可能快地为内存对象解锁。内存需要加锁和解锁增加了程序员的负担,但是它却极大地改善了Windows内存利用的效率,因此Windows鼓励使用可移动和可删除的内存对象,并且要求应用程序在非必要时不要使用固定内存对象。

      
不同类型的对象在它所处的内存堆中的位置是不一样的,图6.2说明内存对象在堆中的位置:固定对象位于堆的底部;可移动对象位于固定对象之上;可删除对象从堆的顶部开始分配。

6.1  内存对象分配位置示意图

 

6.1.2  局部内存对象管理

      
局部内存对象在局部堆中分配,局部堆是应用程序独享的自由内存,它只能由应用程序的特定实例访问。局部堆建立在应用程序的数据段中,因此,用户可分配的局部内存对象的最大内存空间不能超过64K。局部堆由Windows应用程序在模块定义文件中用HEAPSIZE语句申请,HEAPSIZE指定以字节为单位的局部堆初始空间尺寸。Windows提供了一系列函数来操作局部内存对象。

6.1.2.1 分配局部内存对象

      
LocalAlloc
函数用来分配局部内存,它在应用程序局部堆中分配一个内存块,并返回内存块的句柄。LocalAlloc函数可以指定内存对象的大小和特性,其中主要特性有固定的(LMEM_FIXED),可移动的(LMEM_MOVEABLE)和可删除的(LMEM_DISCARDABLE)。如果局部堆中无法分配申请的内存,则LocalAlloc函数返回NULL。下面的代码用来分配一个固定内存对象,因为局部固定内存对象的对象句柄其本身就是16位内存近地址,因此它可以被应用程序直接存取。代码如下:

char NEAR * pcLocalObject;

if 
(pcLocalObject = LocalAlloc(LMEM_FIXED, 32)) {

/* Use pcLocalObject as the near address of the
Locally allocated object, It is not necessary to lock

   and
unlock the fixed local object */

        
.…..

}      

else {

         /*
The 32 bytes cannot be allocated .React accordingly. */

}

6.1.2.2 
加锁与解锁

      
上面程序段分配的固定局部内存对象可以由应用程序直接存取,但是,Windows并不鼓励使用固定内存对象。因此,在使用可移动和可删除内存对象时,就要经常用到对内存对象的加锁与解锁。

      
不管是可移动对象还是可删除对象,在它分配后其内存句柄是不变的,它是内存对象的恒定引用。但是,应用程序无法通过内存句柄直接存取内存对象,应用程序要存取内存对象还必须获得它的近地址,这通过调用LocalLock函数实现。LocalLock函数将局部内存对象暂时固定在局部堆的某一位置,并返回该地址的近地址值,此地址可供应用程序存取内存对象使用,它在应用程序调用 LocalUnlock函数解锁此内存对象之前有效。怎样加锁与解锁可移动内存对象,请看如下代码:

HLOCAL hLocalObject;

char NEAR *pcLocalObject;

if (hLocalObject = LocalAlloc(LMEM_MOVEABLE, 32))
{

         if
(pcLocalObject = LocalLock(hLocalObject)) {

                  
/*Use pcLocalObject as the near address of the locally allocated object
*/

                  
.…..

                  
LocalUnlock(hLocalObject);

        
}

         else
{

                  
/* The lock failed. React accordingly. */

        
}

}

else {

         /*
The 32 bytes cannot be allocated. React accordingly. */

}

      
应用程序在使用完内存对象后,要尽可能早地为它解锁,这是因为Windows无法移动被锁住了的内存对象。当应用程序要分配其它内存时,Windows不能利用被锁住对象的区域,只能在它周围寻找,这会降低Windows内存管理的效率。

6.1.2.3 
改变局部内存对象

      
局部内存对象分配之后,还可以调用LocalReAlloc函数进行修改。LocalReAlloc函数可以改变局部内存对象的大小而不破坏其内容:如果比原来的空间小,则Windows将对象截断;如果比原来大,则Windows将增加区域填0(使用LMEM_ZEROINIT选项),或者不定义该区域内容。另外,LocalReAlloc函数还可以改变对象的属性,如将属性从LMEM_MOVEABLE改为LMEM_DISCARDABLE,或反过来,此时必须同时指定LMEM_MODIFY选项。但是,LocalReAlloc函数不能同时改变内存对象的大小和属性,也不能改变具有LMEM_FIXED属性的内存对象和把其它属性的内存对象改为LMEM_FIXED属性。如何将一个可移动内存对象改为可删除的,请看下面的例子:

hLocalObject = LocalAlloc(32,
LMEM_MOVEABLE);

.…..

hLocalObject = LocalReAlloc(hLocalObject, 32,
LMEM_MODIFY| LMEM_KISCARDABLE);

6.1.2.4 
释放与删除

      
分配了的局部内存对象可以使用LocalDiscardLocalFree函数来删除和释放,删除和释放只有在内存对象未锁住时才有效。

      
LocalFree
函数用来释放局部内存对象,当一个局部内存对象被释放时,其内容从局部堆移走,并且其句柄也从有效的局部内存表中移走,原来的内存句柄变为不可用。LocalDiscard 函数用来删除局部内存对象,它只移走对象的内容,而保持其句柄有效,用户在需要时,还可以使用此内存句柄用LocalReAlloc函数重新分配一块内存。

      
另外,Windows还提供了函数LocalSize用于检测对象所占空间;函数LocalFlags用于检测内存对象是否可删除,是否已删除,及其锁计数值;函数LocalCompact用于确定局部堆的可用内存。

6.1.3  全局内存对象管理

      
全局内存对象在全局堆中分配,全局堆包括所有的系统内存。一般来说,应用程序在全局堆中进行大型内存分配(约大于1KB),在全局堆还可以分配大于64K的巨型内存,这将在后面介绍。

6.1.3.1 
分配全局内存对象

      
全局内存对象使用GlobalAlloc函数分配,它和使用LocalAlloc分配局部内存对象很相似。使用GlobalAlloc的例子我们将和GlobalLock一起给出。

6.1.3.2 
加锁与解锁

      
全局内存对象使用GlobalLock函数加锁,所有全局内存对象在存取前都必须加锁。GlobalLock将对象锁定在内存固定位置,并返回一个远指针,此指针在调用GlobalUnlock之前保持有效。

      
GlobalLock
LocalLock稍有不同,因为全局内存对象可能被多个任务使用,因此在使用GlobalLock加锁某全局内存对象时,对象可能已被锁住,为了处理这种情况,Windows增加了一个锁计数器。当使用GlobalLock加锁全局内存对象时,锁计数器加1;使用GlobalUnlock解锁对象时,锁计数器减1,只有当锁计数器为0时,Windows才真正解锁此对象。GlobalAllocGlobalLock的使用见如下的例子:

HGLOBAL hGlobalObject;

char FAR * lpGlobalObject;

if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE,
1024)) {

         if
(lpGlobalObject = GlobalLock(hGlobalObject)) {

                  
/* Use lpGlobalObject as the far address of the globally allocated
object. */

                  
.…..

                  
GlobalUnlock (hGlobalObject);

        
}

         else
{

                  
/* The lock failed .React accordingly. */

        
}

}

else {

         /*
The 1024 bytes cannot be allocated. React accordingly. */

}

6.1.3.3 
修改全局内存对象

      
修改全局内存对象使用GlobalReAlloc函数,它和LocalReAlloc函数很类似,这里不再赘述。修改全局内存对象的特殊之处在于巨型对象的修改上,这一点我们将在后面讲述。

6.1.3.4 
内存释放及其它操作

      
全局内存对象使用GlobalFree函数和GlobalDiscard来释放与删除,其作用与LocalFreeLocalDiscard类似。GlobalSize函数可以检测内存对象大小;GlobalFlags函数用来检索对象是否可删除,是否已删除等信息;GlobalCompact函数可以检测全局堆可用内存大小。

6.1.3.5 
巨型内存对象

      
如果全局内存对象的大小为64KB或更大,那它就是一个巨型内存对象,使用GlobalLock函数加锁巨型内存对象将返回一个巨型指针。分配一个128KB的巨型内存对象,使用下面的代码段:

HGLOBAL hGlobalObject;

char huge * hpGlobalObject;

if (hGlobalObject = GlobalAlloc(GMEM_MOVEABLE,
0x20000L)) {

         if
(hpGlobalObject = (char huge *)GlobalLock(hGlobalObject)) {

                  
/* Use hpGlobalObject as the far address of the globally allocated
object. */

                  
...

                  
GlobalUnlock (hGlobalObject);

        
}

         else
{

                  
/* The lock failed. React accordingly. */

        
}

}

else {

         /*
The 128K cannot be allocated. React accordingly. */

}

      
巨型内存对象的修改有一点特殊性,当对象大小增加并超过64K的倍数时,Windows可能要为重新分配的内存对象返回一个新的全局句柄,因此,巨型内存对象的修改应采用下面的形式:

if (hTempHugeObject =
GlobalReAlloc(hHugeObject,0x20000L,GMEM_MOVEABLE)){

        
hHugeObject = hTempObject;

}

else {

         /*
The object could not be Reallocated. React accordingly. */

}

6.1.4 

      
Windows
采用段的概念来管理应用程序的内存,段有代码段和数据段两种,一个应用程序可有多个代码段和数据段。代码段和数据段的数量决定了应用程序的内存模式,图6.2说明了内存模式与应用程序代码段和数据段的关系。

 

 

代码段数

单段

多段

数据段数

单段

小内存模式

中内存模式

多段

压缩内存模式

大内存模式

6.2  内存模式图

 

      
段的管理和全局内存对象的管理很类似,段可以是固定的,可移动的和可删除的,其属性在应用程序的模块定义文件中指定。段在全局内存中分配空间,Windows鼓励使用可移动的代码段和数据段,这样可以提高其内存利用效率。使用可删除的代码段可以进一步减小应用程序对内存的影响,如果代码段是可删除的,在必要时Windows将其删除以满足对全局内存的请求。被删除的段由Windows监控,当应用程序利用该代码段时,Windows自动地将它们重新装入。

6.1.4.1 
代码段

      
代码段是不超过64K字节的机器指令,它代表全部或部分应用程序指令。代码段中的数据是只读的,对代码段执行写操作将引起通用保护(GP)错误。

      
每个应用程序都至少有一个代码段,例如我们前面几章的例子都只有一个代码段。用户也可以生成有多个代码段的应用。实际上,多数Windows应用程序都有多个代码段。通过使用多代码段,用户可以把任何给定代码段的大小减少到完成某些任务所必须的几条指令。这样,可通过使某些段可删除,来优化应用程序对内存的使用。

      
中模式和大模式的应用程序都使用多代码段,这些应用程序的每一个段都有一个或几个源文件。对于多个源文件,将它们分开各自编译,为编译过的代码所属的每个段命名,然后连接。段的属性在模块定义文件中定义,Windows使用SEGMENTS语句来完成此任务,如下面的代码定义了四个段的属性:

SEGMENTS

MEMORY_MAIN   
       PRELOAD    MOVEABLE

MEMORY_INIT   
          LOADONCALL MOVEABLE
DISCARDABLE

MEMORY_WNDPROC PRELOAD    MOVEABLE

MEMORY_ABOUT  
      LOADONCALL MOVEABLE
DISCARDABLE

      
用户也可以在模块定义文件中用CODE语句为所有未显式定义过的代码段定义缺省属性。例如,要将未列在SEGMENTS语句中的所有段定义为可删除的,可用下面的语句:

CODE MOVEABLE DISCARDABLE

6.1.4.2 
数据段

      
每个应用程序都有一个数据段,数据段包含应用程序的堆栈、局部堆、静态数据和全局数据。一个数据段的长度也不能超过64K。数据段可以是固定的或可移动的,但不能是可删除的。如果数据段是可移动的,Windows在将控制转向应用程序前自动为其加锁,当应用程序分配全局内存,或试图在局部堆中分配超过当前可分的内存时,可移动数据段可能被移动,因此在数据段中不要保留指向变量的长指针,当数据段移动时,此长指针将失效。

      
在模块定义文件中用DATA语句定义数据段的属性,属性的缺省值为MOVEABLEMULTIPLEMULTIPLE属性使Windows为应用程序的每一个实例拷贝一个应用程序数据段,这就是说每个应用程序实例中数据段的内容都是不同的。

抱歉!评论已关闭.