C++内存管理结构

来源:互联网 发布:2016年淘宝店铺推广 编辑:程序博客网 时间:2024/05/13 10:53
 mainCRTStartup 的头几步工作是初始化一些预定义的全局变量, _osplatform, _winmajor, _winminor, _osver, _winver等与Windows Version 有关的. 然后很重要的一步就是初始化 Heap. 大多的内存都是 Heap 上分配的, 如 new, malloc. 所以必须要先初始化才能干别的. 这个函数是_heap_init. 以前的Compiler 有些用自己的 Heap 管理, 但是现在我手上的 Visual Studio .NET 2003, 完全是用的 Windows 系统提供的 Heap management. 所以 _heap_init 很简单:

int __cdecl _heap_init (int mtflag)
{
    // Initialize the "big-block" heap first.
    if ( (_crtheap = HeapCreate( mtflag ? 0 : HEAP_NO_SERIALIZE,
    BYTES_PER_PAGE, 0 )) == NULL )
    return 0;
    ...
}

系统默认的 new, malloc 等等的分配都是在这个_crtheap 上进行的. 试试写个简单的程序:

int main()
{
    int* p = new int;
    return 0;
}

在int* p = new int; 这一行设个断点, 调试进去. 可以看见new 是这样的:

void * operator new( size_t cb )
{
    void *res = _nh_malloc( cb, 1 );
    ...
    return res;
}

下面以Debug version为例, 因为Debug version比较有意思. _nh_malloc 只是简单调用_nh_malloc_dbg, 而malloc 也是调用_nh_malloc_dbg来完成内存分配.

_nh_malloc_dbg最终调用_heap_alloc_dbg, 在这里进行真正的分配工作. Debug Version中, 实际分配的是这样一个结构:

typedef struct _CrtMemBlockHeader
{
    struct _CrtMemBlockHeader * pBlockHeaderNext;
    struct _CrtMemBlockHeader * pBlockHeaderPrev;
    char * szFileName;
    int nLine;
    size_t nDataSize;
    int nBlockUse;
    long lRequest;
    unsigned char gap[nNoMansLandSize];
    /* followed by:
    * unsigned char data[nDataSize];
    * unsigned char anotherGap[nNoMansLandSize];
    */
} _CrtMemBlockHeader;

假如你要分配一个大小为100的块, 则实际分配的块结构如下:

_CrtMemBlockHeader + <You Data> (100 bytes) + gap[nNoMansLandSize]

_CrtMemBlockHeader 最后有个gap[nNoMansLandSize], 这个nNoMansLandSize目前的值是 4, 所以在你的数据前后各有4个字节的 gap. _heap_alloc_dbg会把 <Your Data> 的所有字节置为 0xCD, 前后的gap置成 0xFD. 如果你在自己的Data里写, 不小心越了界(前面或者后面), 系统在delete的时候通过检查 gap 的数据是否已被破坏,就知道你有没有越界. 当然了, 如果你恰好写的都是0xFD, 那就没法知道了. 试试如下程序: 

int* p = new int;
p[1] = 0;
delete p;

在Debug 下运行时,delete 时系统会报错: DAMAGE: after normal block (#59) at 0x00375C80.

_heap_alloc_dbg 调用 Windows System Call HeapAlloc 完成分配. HeapAlloc返回的指针, 要先初始化 _CrtMemBlockHeader. 这个 Header 中有前后两个指针, 事实上所有的内存块连接在一起形成一个双向链表. 在 delete 或者 free 的时候, 链表指针需要调整. 如果没有内存泄漏, 程序结束的时候链表应该为空. 否则说明有内存泄漏. 如下面的程序:

#include <stdio.h>
#include <stdlib.h>
#include <crtdbg.h>

int main()
{
    int* p = new int;
    //delete p;

    void *px = malloc(100);
    free(px);

    _CrtDumpMemoryLeaks();
    return 0;
}

_CrtDumpMemoryLeaks 通过检查分配链表, 来查找是否有泄漏. 在 Debug 下编译并且在 VC 中跟踪运行, 最后在 VC 的 Output 中会有如下输出:

Detected memory leaks!
Dumping objects ->
{58} normal block at 0x00375FC0, 4 bytes long.
Data: < > CD CD CD CD
Object dump complete.

这里只有分配的序号, 还不能知道到底是哪一行程序产生的泄漏. 但是注意看_CrtMemBlockHeader, 事实上它还能记录源程序文件名和行号. 在 MFC 里就利用了这个技术. 在 afx.h 里, 有如下声明:

// Memory tracking allocation
void* AFX_CDECL operator new(size_t nSize, LPCSTR lpszFileName, int nLine);
#define DEBUG_NEW new(THIS_FILE, __LINE__)

同时,使用 MFC 时产生的 cpp 文件开始都有如下定义:

#ifdef _DEBUG
#define new DEBUG_NEW

这个 afx 的 new operator 把 new 时发生的源文件和行号传给 _malloc_dbg .这样在 Dump memory leak 的时候就可以同时知道泄漏的数据最初是在什么地方分配的.

delete 和 free 最终都是用的 _free_dbg. _free_dbg 首先检查前后的 gap 有没有被破坏, 然后把该块从链表中去掉, 最后把数据块全部置成 0xDD.这样如果你不小心使用了已经被删除的数据时,通常数据已经被破坏而出错.

以上说的都是 Debug Version. 如果是 Release version, 内存分配更简单, 没有任何 overhead, 系统直接调用 HeapAlloc 分配所需的内存块. 同时分配的内存块也不会被初始化为 0xCD.
原创粉丝点击