进程默认堆和额外创建的堆

Posted HsinTsao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了进程默认堆和额外创建的堆相关的知识,希望对你有一定的参考价值。

在《Windows核心编程》第五版的第十八章 《堆》 中提到了进程默认堆和额外创建的堆。这在编程中是十分重要的知识,今天整理一下。

1,堆非常适合分配大量的小型数据。使用堆可以让程序员专心解决手头的问题,而不必理会分配粒度和页面边界之类的事情。因此堆是管理链表和数的最佳方式。但是堆进行内存分配和释放时的速度比其他方式都慢,而且无法对物理存储器的调拨和撤销调拨进行控制。

为了能适应各种硬件平台,如果程序员想要控制物理存储器的调拨和撤销调拨,就不应该使用堆,而应该使用虚拟内存(VirtualAlloc和VirtualFree)。

 

2.进程默认堆。 

进程初始化时,系统会在进程的地址空间中创建一个堆。这个堆被称为进程的默认堆。默认情况下,这个堆的地址空间区域大小是1MB。程序员可以控制这个大小。我们可以在创建应用程序时用/HEAP连接器开关来改变默认堆的大小。由于DLL没有与之关联的堆,因此在创建DLL时,不应该使用/HEAP开关。

我们可以通过调用GetProcessHeap来得到进程默认堆

 

3.为什么要创建额外的堆?

(1)对组件进行保护
如:有一个链表和一个二叉树都需要保存在同一个堆中,这会使两种结构混合,假如链表中的某个节点出错覆盖他下边的某块内存,就可能把二叉树结构破坏,这会导致定位出错很难,于是把两种结构放到两个堆中,这种可能性就会小很多。
(2)更有效的内存管理
每个堆保证相同的结构会使内存分配更为合理。再举上一个例子,链表每个节点大小为24字节,二叉树每个节点32字节,现在链表释放了2个节点,这时有了48字节的空间,但是很有可能这两块内存不连续。而没法给二叉树分配一个节点的空间。如此操作,会使堆内内存碎片化。
(3)使内存访问局部化
我们知道系统在访问内存的时候会产生页交换,当系统把一个内存页换入到磁盘中,再从磁盘中换出一个内存页时,这种消耗是十分巨大的。于是,把相同的节点放在一个内存堆中就显得尤为重要了。继续举栗子,当我们一个堆中只保存链表节点,或者只保存二叉树节点,这个时候内存很有可能分配到邻近一块儿内存中,这时候我们遍历节点就不需要再进行页交换了。但是两个结构混用的话,很有可能看似相邻的两个节点其实在两个页交换文件中,如果运气差,有可能进行一次遍历,系统需要多次进行页交换,大大降低了效率。
(4)避免线程同步的开销
在默认情况下,系统中线程对堆的访问是依次进行的,如果你自己创建了堆,系统将不会用额外代码来保证线程的安全性,就会在一定程度上减小系统的消耗,不过这时候对堆的内存操作将有我们自己进行,系统将不再管理。
(5)快速释放
如果我们创建一个堆来存放数据,我们在销毁数据的时候可以仅仅将整个堆的内存释放,而不必一个个节点去释放,大大加快了释放内存的效率。
 
4.如何创建额外的堆
HANDLE HeapCreate(

     DWORD fdwOptions,

     SIZE_T dwInitilialize,

     SIZE_T dwMaximumSize);

5.堆创建后,需要从堆中分配内存时,要调用HeapAlloc函数:

PVOID HeapAlloc(

     HANDLE hHeap,

     DWORD fdwFlags,

     SIZE_T dwBytes);

如果分配成功HeapAlloc会返回内存块地址。否则将会返回NULL。

默认情况下,对堆的访问会依次进行。当任何程序试图从堆中分配一块内存时,HeapAlloc会执行以下操作:

     1:遍历已分配的内存的链表和闲置内存的链表。

     2:找到一块足够大的闲置内存块。

     3:分配一块新的内存,将2找到的内存块标记为已分配。

     4:将新分配的内存块添加到已分配的链表中。

     注意:在分配大于1MB的内存时应该避免使用堆函数,而应该使用VirtualAlloc函数。

 

6.调整内存块大小

PVOID HeapReAlloc(

     HANDLE hHeap,

     DWORD fdwFlags,

     PVOID pvMem,

     SIZE_T dwBytes);

7.获得内存块大小

SIZE_T HeapSize(

    HANDLE hHeap,

    DWORD fdwFlags,

    LPCVOID pvMem);

8.释放内存块

BOOL HeapFree(

    HANDLE hHeap,

    DWORD fdwFlags,

    PVOID pvMem);

9.销毁堆

BOOL HeapDestroy(HANDLE hHeap); 

10.其他

函数GetProcessHeaps返回一个HANDLE数组   可获得包括默认堆在内的所有堆的句柄

 

一下两个函数要配对使用,用于线程同步:

BOOL HeapLock(HANDLE hHeap);

BOOL HeapUnlock(HANDLE hHeap);

 

让堆中闲置的内存块能重新结合在一起,并撤销调拨给堆中闲置内存块的存储器,可以调用HeapCompact:

UINT HeapCompact(

    HANDLE hHeap,

    DWORD fdwFlags);

 

HeapValidate可以验证堆的完整性。

BOOL HeapValidate(

    HANDLE hHeap,

    DWORD fdwFlags,

    LPCVOID pvMem);

 


 

以上是关于进程默认堆和额外创建的堆的主要内容,如果未能解决你的问题,请参考以下文章

面试题:堆和栈的区别

java中的堆和栈

进程线程和协程的区别

java中的堆和栈

值类型和引用类型的区别 I 数据结构中的堆和栈和内存中的堆和栈的区别

Java 中的堆和栈