C++内存管理结构

本文详细介绍了C++中内存管理的基础知识,特别是针对Debug版本如何实现内存分配与追踪,包括_heap_init的初始化过程、内存分配时的具体结构及Debug与Release版本的区别。

 C++内存管理结构

 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.
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值