debug模式的malloc

背景:我们以VS2005作为环境,安装目录是C:/Program Files/Microsoft Visual Studio 8 ,我们称C:/Program Files/Microsoft Visual Studio 8/VC/ 目录为VC目录,称C:/Program Files/Microsoft Visual Studio 8/VC/crt 目录为运行时目录,在这个目录下面有C运行时库的源码C:/Program Files/Microsoft Visual Studio 8/VC/crt/src。

(一下代码基于VS2005的C运行时库,其他版本略有不同)

假设这样的语句:char* p = (char*)malloc(4); malloc最终调用dbgheap.c中的heap_alloc_dbg.

我们将重点关注这三个变量。

 

在dbghook.c中定义_pfnAllocHook, 这是申请内存的一个钩子函数指针,默认指向_CrtDefaultAllocHook。默认函数什么也不干,直接返回1。我们可以通过调用_CrtSetAllocHook函数设置我们自己的函数,这样的话,在每次分配内存之前都会回调一次我们的函数。上面是分配接口中调用这个函数的地方。

 

这句是说,如果没有设置_crtDbgFlag的 _CRTDBG_ALLOC_MEM_DF位,则忽略调试信息。

正是在_heap_alloc_base中调用了HeapAlloc申请的内存。申请的大小并不是我们所请求的nSize,而是nSize + sizeof(_CrtMemBlockHeader) + nNoMansLandSize。也就是说,我们每次申请一块内存,都会有额外的内存被分配,其中_CrtMemBlockHeader是用来维护一个链表,链表的每一个节点就是一次申请的情况,nNoMansLandSize一般是四字节的区域。

很显然,把新的节点插入链表的表头,_pFirstBlock指向表头。pFirstBlock全局变量在dbgheap.c中定义。

再让我们看看这个关系图

由于_CrtMemBlockHeader的最后,也就是DATA的头,也有一个nNoMansLandSize大小的区域gap(从_CrtMemBlockHeader的定义可以看到)。函数最后返回的是pbData(pHead),也就是DATA的首地址,这就是我们实际可以操作的内存,而这块内存的前后被nNoMansLandSize大小的gap包裹着。

这两个位置被初始化为_bNoMansLandFill,这是0xFD.作用是用来判断用户操作地址的时候,是否越界。

而实际用户空间DATA初始化为_bCleanLandFill,这是0xCD.(可以测试一下,申请一个空间,看看是不是全部是0xCD.)

_pFirstBlock表头并没有向用户开放,要想获得这个表头指针从而遍历整个表,可以这样操作:申请一个任意大小的内存(通常为1),这块内存被放到表头,把获得的地址向前偏移一个_CrtMemBlockHeader,就可以得到当前表头指针,也就可以得到第二个节点的地址,记录下第二个节点的地址,然后释放刚才分配的内存,这样本来是第二个节点,就变成了表头。

void* p = malloc(1);

_CrtMemBlockHeader* pHeader = (((_CrtMemBlockHeader*)p) - 1)-> pBlockHeaderNext;

delete p;

猜你喜欢

转载自blog.csdn.net/bwmwm/article/details/6411236