先举一个例子:
void memory_func1()
{
int *p = new int (5);
delete p;
*p = 1;
//lots of code ...
char *pc = new char (3); //aha, crash
}
当运行到 char *pc = new char (3); 代码会 crash 掉,因为前面给已经释放的内存赋值。当这种情况发生时 ( 给已经释放的内存赋值 ) ,为什么会在下一次 new 时程序 crash 呢?
New 的工作原理: (VC, Debug)
实验代码:
void memory_func2()
{
int *p = new int ;
int *p2 = new int ;
int *p3 = new int ;
delete p;
delete p2;
delete p3;
*p = 1;
//lots of code ...
char *pc = new char ; //aha, crash
}
在前三次内存分配中,内存的内容的对比如下:
原始 (左 ) 与 第一次 new(右 )
第一次 new (左 ) 与 第二次 new(右 )
第二次 new (左 ) 与 第三次 new(右 )
可以看到,每一次 new int,内存的改动量挺大的。那么 new底层都作了什么?
F11跟进代码!
1:
int *p = new int ;
2:
void *__CRTDECL operator new (size_t size) _THROW1(_STD bad_alloc)
{ // try to allocate size bytes
void *p;
while ((p = malloc(size)) == 0)
……
3:
extern "C" _CRTIMP void * __cdecl malloc (
size_t nSize
)
{
void *res = _nh_malloc_dbg(nSize, _newmode, _NORMAL_BLOCK, NULL, 0);
…… 一堆函数调用,直到这里
4:
//一堆内存块检查
blockSize = sizeof (_CrtMemBlockHeader) + nSize + nNoMansLandSize;
/* 说明:
这里 blockSize = 40,
CrtMemBlockHeader 的结构如下:
pBlockHeadPrev | 指针 , 指向前一个分配内存块 |
pBlockHeadNext | 指针, … 下一个 … |
szFileName | 指针,指向文件,估计用于内存影射文件,如果使用 new 等从内容分配则为 NULL |
nLine |
|
nDataSize |
|
nBlockUse |
|
lRequest |
|
Gap[4] | 填充 {0xfd,…} |
他的大小应该是 32
nSize = 4 , 是要开辟的空间大小 , 在这里 ==sizeof(int)
nNoMansLandSize 未查到值,但根据上下文,应该是 4
*/
// 开始获取内存头块,并设置内容
pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);
……
++_lRequestCurr;
_lTotalAlloc += nSize;
_lCurAlloc += nSize;
if (_lCurAlloc > _lMaxAlloc)
_lMaxAlloc = _lCurAlloc;
if (_pFirstBlock)
_pFirstBlock->pBlockHeaderPrev = pHead;
Else
_pLastBlock = pHead;
pHead->pBlockHeaderNext = _pFirstBlock;
pHead->pBlockHeaderPrev = NULL;
pHead->szFileName = (char *)szFileName;
pHead->nLine = nLine;
pHead->nDataSize = nSize;
pHead->nBlockUse = nBlockUse;
pHead->lRequest = lRequest;
/* link blocks together */
_pFirstBlock = pHead;
/*
经过上面调用后, pHead(0x003a9630) 的内容:
pBlockHeadPrev | 0x003a91a8 |
pBlockHeadNext | 0x00000000 |
szFileName | 0x00000000 |
nLine | 0 |
nDataSize | 4 |
nBlockUse | 1 |
lRequest | 88 |
Gap[4] | …… |
*/
// 这里是为 Debug 所作的在内存边界填充 bNoMansLandFill(0xfd) ,我认为是为了调试内存越界的吧
memset((void *)pHead->gap, _bNoMansLandFill, nNoMansLandSize);
memset((void *)(pbData(pHead) + nSize), _bNoMansLandFill, nNoMansLandSize);
// 然后把开辟的内存填充 bCleanLandFill (0xcd)
/* fill data with silly value (but non-zero) */
memset((void *)pbData(pHead), _bCleanLandFill, nSize);
上面就是分配的全过程,完成后内存内容如下:
(注:图中 prev和 next标反了! )
如果对上面过程进行总结,就是
如果要开辟的空间大小为 sizeof(int) = 4,
那么实际系统会申请大小为 32 + 4 + 4的空间,
其中前 32的大小为内存块头,存储内存分配信息,其中最后 4字节用 0xfd填充
中间 4字节为给用户的内存空间,在 debug下会用 0xcd填充
最后 4字节用 0xfd填充
这里面还有个关键的
pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);
这里面又有什么过程?
跟进去后发现是这样:
#ifdef _WIN64
return HeapAlloc(_crtheap, 0, size ? size : 1);
#else /* _WIN64 */
if (__active_heap == __SYSTEM_HEAP) {
return HeapAlloc(_crtheap, 0, size ? size : 1);
……
调用了系统得 HeapAlloc 函数。
以上是在 Windows 下 new 的 debug 实现过程。