最近去了一次面试,面试官问道这个进程的自建堆和默认堆的相关问题,我当时感觉答的并不是很好,这时候我决定好好整理一下相关知识跟大家分享。
进程默认堆:
Windows许多重要的函数都用Unicode字符和Unicode字符串来执行它们所有的操作。如果我们调用的是一个函数的ANSI版本,那么该函数的ANSI版本必须把ANSI字符串转换为Unicode字符串,然后再调用同一个函数的Unicode版本。为了转换字符串,ANSI版本的函数需要分配一块内存来保存Unicode版本的字符串,这块内存就是从进程的默认堆中分配的。默认堆大小一般为1M,可以在编译器设置/HEAP。
进程自建堆:
为什么要创建额外的堆:
1.对组件进行有效的保护
2.更有效的内存管理
3.使内存访问局部化
4.避免线程的同步开销
5.快速释放
1.对组件进行保护
举个栗子:有一个链表和一个二叉树都需要保存在同一个堆中,这会使两种结构混合,假如链表中的某个节点出错覆盖他下边的某块内存,就可能把二叉树结构破坏,这会导致定位出错很难,于是把两种结构放到两个堆中,这种可能性就会小很多。
2.更有效的内存管理:
每个堆保证相同的结构会使内存分配更为合理。再举上一个栗子,链表每个节点大小为24字节,二叉树每个节点32字节,现在链表释放了2个节点,这时有了48字节的空间,但是很有可能这两块内存不连续。而没法给二叉树分配一个节点的空间。如此操作,会使堆内内存碎片化。
3.使内存访问局部化
我们知道系统在访问内存的时候会产生页交换,当系统把一个内存页换入到磁盘中,再从磁盘中换出一个内存页时,这种消耗是十分巨大的。于是,把相同的节点放在一个内存堆中就显得尤为重要了。继续举栗子,当我们一个堆中只保存链表节点,或者只保存二叉树节点,这个时候内存很有可能分配到邻近一块儿内存中,这时候我们遍历节点就不需要再进行页交换了。但是两个结构混用的话,很有可能看似相邻的两个节点其实在两个页交换文件中,如果运气差,有可能进行一次遍历,系统需要多次进行页交换,大大降低了效率。
4.避免线程同步的开销
在默认情况下,系统中线程对堆的访问是依次进行的,如果你自己创建了堆,系统将不会用额外代码来保证线程的安全性,就会在一定程度上减小系统的消耗,不过这时候对堆的内存操作将有我们自己进行,系统将不再管理。
5.快速释放
如果我们创建一个堆来存放数据,我们在销毁数据的时候可以仅仅将整个堆的内存释放,而不必一个个节点去释放,大大加快了释放内存的效率。
如何创建额外的堆
我们可以调用HeapCreate函数来创建额外的堆。函数原型如下:
HANDLE WINAPI HeapCreate( _In_ DWORD flOptions, //表示对堆的操作应该如何进行 _In_ SIZE_T dwInitialSize, //表示一开始系统调拨给堆的字节数,如果需要系统会将这个值向上取整到CPU页面的整数倍 _In_ SIZE_T dwMaximumSize //向上取整最大字节数 );