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.
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.