使用C++的程序员都很熟悉使用new来分配一块内存,但是new函数具体是怎么工作的?可能很多人都没有花时间去研究,今天花了半天时间,查阅了相关的一些源代码,对它的来龙去脉有了一个初步的了解,写得不是很详细,一个是记录一下,另一个就是抛砖引玉,有兴趣的人可以深入研究。如果有不对的地方,也欢迎大家指教。
由于文章较长,分成四个部分。
文中列出了讲解所用到的源代码,仅供大家参考。
好,接下来就开始我们的“new”之旅:
分配一段内存首先当然是调用new函数去分配,我们就从new函数出发:
void * operator new( unsigned int cb )
{
return _nh_malloc( cb, 1 );
}
(注:也有别的版本的new,但对后面的分析没有影响)
可以看出,new只是简单地调用了_nh_malloc, 其中第一个参数cb是要分配的大小,第二个参数是有没有new handler,使用new时传入了1,表示是有new handler的,关于new handler后面还会提到,但它的作用和使用方法不在本文范围内,可以自行在网上查找。
void * __cdecl _nh_malloc (
size_t nSize,
int nhFlag
)
{
return _nh_malloc_dbg(nSize, nhFlag, _NORMAL_BLOCK, NULL, 0);
}
这个函数也只是调用了它的debug版_nh_malloc_dbg。接着往下看这个函数,这个函数主要是一个无限循环的for语句块,有三种返回的情况:
1 成功分配一块内存;
2 分配失败且没有new handler设置;
3 分配失败且调用_callnewh返回0。
从下面的代码我们也能知道,使用new去分配一块内存时,如果失败的话会调用new handler,然后接着去尝试分配内存,直到分配成功或_callnewh返回0。
void * __cdecl _nh_malloc_dbg (
size_t nSize,
int nhFlag,
int nBlockUse,
const char * szFileName,
int nLine
)
{
void * pvBlk;
for (;;)
{
/* lock the heap
*/
_mlock(_HEAP_LOCK);
/* do the allocation
*/
pvBlk = _heap_alloc_dbg(nSize, nBlockUse, szFileName, nLine);
/* unlock the heap
*/
_munlock(_HEAP_LOCK);
if (pvBlk || nhFlag == 0)
return pvBlk;
/* call installed new handler */
if (!_callnewh(nSize))
return NULL;
/* new handler was successful -- try to allocate again */
}
}
_callnewh函数源代码,就是去调用new handler函数, _pnhHeap是一个函数指针。
extern "C" int __cdecl _callnewh(size_t size)
{
{
_PNH pnh = _pnhHeap;
if ( (pnh == NULL) || ((*pnh)(size) == 0) )
return 0;
}
return 1;
}
可以看出分配内存的事是在函数_heap_alloc_dbg做的,我们再看看这个函数。
#define pbData(pblock) ((unsigned char *)((_CrtMemBlockHeader *)pblock + 1))
static unsigned char _bNoMansLandFill = 0xFD;
void * __cdecl _heap_alloc_dbg(
size_t nSize,
int nBlockUse,
const char * szFileName,
int nLine
)
{
long lRequest;
size_t blockSize;
int fIgnore = FALSE;
_CrtMemBlockHeader * pHead;
/* verify heap before allocation */
if (_crtDbgFlag & _CRTDBG_CHECK_ALWAYS_DF)
_ASSERTE(_CrtCheckMemory());
lRequest = _lRequestCurr;
/* break into debugger at specific memory allocation */
if (lRequest == _crtBreakAlloc)
_CrtDbgBreak();
/* forced failure */
if (!(*_pfnAllocHook)(_HOOK_ALLOC, NULL, nSize, nBlockUse, lRequest, szFileName, nLine))
{
if (szFileName)
_RPT2(_CRT_WARN, "Client hook allocation failure at file %hs line %d.\n",
szFileName, nLine);
else
_RPT0(_CRT_WARN, "Client hook allocation failure.\n");
return NULL;
}
/* cannot ignore CRT allocations */
if (_BLOCK_TYPE(nBlockUse) != _CRT_BLOCK &&
!(_crtDbgFlag & _CRTDBG_ALLOC_MEM_DF))
fIgnore = TRUE;
/* Diagnostic memory allocation from this point on */
if (nSize > (size_t)_HEAP_MAXREQ ||
nSize + nNoMansLandSize + sizeof(_CrtMemBlockHeader) > (size_t)_HEAP_MAXREQ)
{
_RPT1(_CRT_ERROR, "Invalid allocation size: %u bytes.\n", nSize);
return NULL;
}
if (!_BLOCK_TYPE_IS_VALID(nBlockUse))
{
_RPT0(_CRT_ERROR, "Error: memory allocation: bad memory block type.\n");
}
blockSize = sizeof(_CrtMemBlockHeader) + nSize + nNoMansLandSize;
#ifndef WINHEAP
/* round requested size */
blockSize = _ROUND2(blockSize, _GRANULARITY);
#endif /* WINHEAP */
pHead = (_CrtMemBlockHeader *)_heap_alloc_base(blockSize);
if (pHead == NULL)
return NULL;
/* commit allocation */
++_lRequestCurr;
if (fIgnore)
{
pHead->pBlockHeaderNext = NULL;
pHead->pBlockHeaderPrev = NULL;
pHead->szFileName = NULL;
pHead->nLine = IGNORE_LINE;
pHead->nDataSize = nSize;
pHead->nBlockUse = _IGNORE_BLOCK;
pHead->lRequest = IGNORE_REQ;
}
else {
/* keep track of total amount of memory allocated */
_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;
}
/* fill in gap before and after real block */
memset((void *)pHead->gap, _bNoMansLandFill, nNoMansLandSize);
memset((void *)(pbData(pHead) + nSize), _bNoMansLandFill, nNoMansLandSize);
/* fill data with silly value (but non-zero) */
memset((void *)pbData(pHead), _bCleanLandFill, nSize);
return (void *)pbData(pHead);
}
抛开其它的一些辅助设置和操作,我们主要是关心在什么地方分配内存,返回的指针是pHead, 而pHead是调用了函数 _heap_alloc_base得到地址,然后设置一些参数,设置几位为FD,设置nsize个0,返回这个地址。可见是在_heap_alloc_base做的分配内存的事。如果有兴趣可以new一块内存,看看内存中的值为多少?