最近的项目涉及到Heap Corruption的问题,所以对堆要有更深的理解。
进程初始化时会被分配一个默认大小为1M的默认堆,这个堆会被很多重要的函数调用,比如当我们调用ANSI版本的某些函数时,它们的Unicode版本字符串就会存于其中。若应用程序中有多个线程都用到了默认堆,那么会有机制使得同时只能有一线程能在默认堆中进行操作。默认堆的分配和销毁都是由系统控制的,但是我们可以通过GetPreocessHeap()来得到本进程的默认堆句柄。
常用的分配函数有VirtualAlloc和HeapAlloc.VirtualAlloc请求4K为边界的整块虚拟内存,HeapAlloc分配任意大小的内存块。但后者是依赖前者实现的。也就是说在操作系统的层面上管理内存的最小单位是4K。要实现更小的内存管理(即HeapAlloc),需要用户态的程序自己去分配,比如说Windows的HeapManager。在分配时先用分配足够大的4K倍数的空间,再去进行内部的分配和回收。一般而言,对于小于1M的地址空间,我们一般使用HeapCreate(),但是更大的话,会倾向于用VirtualAlloc()在虚拟内存中分配。
以Alloc结尾的函数都只是分配堆空间,而真正的要去创建一个堆,要使用HeapCreate()。HeapCreate()会返回一个新堆的句柄,而各种alloc函数可以利用这个句柄在不同的堆空间上进行内存分配。在不同的堆上进行alloc可以有效的避免内存碎片的问题,因为当某一个堆中的内容不再需要时,我们可以将这个堆整个的HeapDestroy()掉。但是,若各种数据都存于一个堆中,则要Destroy整个堆必须保证所有的数据都不再需要。
当我们先把一个地址空间HeapFree之后,若HeapManager没有进行VirtualFree的操作,再次访问该地址操作系统并不会报错。因为以HeapFree和HeapAlloc都是由HeapManager来控制的,而操作系统一般情况下的界定粒度为4k。如果只是在HeapManager的控制范围内发生了越界,操作系统可能并不会认为这有什么错误。(因为没有在以4k为粒度的内存上越界)。
在各种create中,经常会有HEAP_NOSERIALIZE这个标志,它的作用是对堆进行线程访问控制。若这个标志被加到参数中,那么同一时刻,可以有多个线程对同一个堆进行堆操作。反之亦然。如果我们不用这个参数,还有两个可用函数HeapLock()和HeapUnlock()达到类似效果。