您的位置:首页 > 编程语言 > C语言/C++

一个C++的内存池和内存管理的实现(六)

2015-04-18 16:40 615 查看
这章是加入内存泄露的自动查找的。之前已经发布了,CSDN不知道给整哪去了,我自己也没存档。懒得再仔细重写了,随便写一下。

泄露工具有不少,比如微软自己的
_CrtSetDbgFlag
_CrtSetBreakAlloc
。但是牵涉到静态变量和跨DLL的申请释放时,很多工具会给出不准确的信息。自己做一个吧,调试起来做改动也方便,可以做到有的放矢。

首先得在前缀里加几个字段。不过加了之后会比较臃肿,所以用个
USE_SMART_HEAP
宏控制,不需要作检查的时候就关掉。
nRefCnt
szToken
bIsArray
是以后越界检查才用到的,现在不用管它。

_declspec(align(ALIGNMENT)) struct PrefixNest {
...
MemPageHeader* pPage;      // page that contains the memory block
#if defined USE_SMART_HEAP
char szFileName[32];       // 文件名
int nLineNumber;           // 行号
int nRefCnt;               // 引用数(类似智能指针)
char szToken[4];           // signature for being a Prefix
bool bIsArray;             // 是否数组
#endif
};


然后MemPool加几个相关函数的定义

class MemPool
{
...
private:
#if defined USE_SMART_HEAP
bool ArePagesAllClear();  // 是否所有页都已回收完毕其下的内存块
void RenderDesc(Prefix*, char*, int);  // 输出泄露信息
#endif
...
public:
#if defined USE_SMART_HEAP
// Display a symbolic dump of the heap (i.e. memory pool) by walking the heap and displaying all objects in the heap.
void WalkHeap();
// Does the given memory pointer point anywhere into the heap.
bool IsPtrOk(void* pMem);
// Verify a given memory pointer
bool VerifyHeapPointer(void* pMem);
#endif
...
}


接下来在内存申请的时候,添加语句用来记录相关文件名和行数。以下所有泄露检查的相关添加和改动都在
#if defined USE_SMART_HEAP
#endif
的宏块里。

void* MemPool::Alloc(int nSize, const char* const szFileName, int nLine, bool bIsArray)
{
if (nSize < MAX_ALLOC_FROM_PAGE)
{
...
// Check if there is free memory in block pool of the corresponding size
if (m_ppBlockPools[i]->d.pNext)  // an available memory block in pool
{
...
pPrefix->d.pPostfix->nActualSize = nSize;  // actual size requested
#if defined USE_SMART_HEAP
if (szFileName) {
strcpy_s(pPrefix->d.szFileName, 31, szFileName); pPrefix->d.szFileName[31] = 0;
} else
pPrefix->d.szFileName[0] = 0;  // null string
pPrefix->d.nLineNumber = nLine;
pPrefix->d.bIsArray = bIsArray;
pPrefix->d.nRefCnt = 0;
#endif
...
}

// Allocate from the page
...
do {
for (MemPageHeader *pPage = m_pFirstPage; pPage != NULL; pPage = pPage->d.pNext)
{
if (pPage->d.pEnd >= pPage->d.pLast + sizeof(Prefix) + nAllocSize + sizeof(Postfix))
{
...
pPrefix->d.pPage = pPage;
#if defined USE_SMART_HEAP
if (szFileName) {
strcpy_s(pPrefix->d.szFileName, 31, szFileName); pPrefix->d.szFileName[31] = 0;
} else
pPrefix->d.szFileName[0] = 0;  // null string
pPrefix->d.nLineNumber = nLine;
pPrefix->d.bIsArray = bIsArray;
*((int*) pPrefix->d.szToken) = PREFIX_SIG;  // 加个签名,表明是个前缀
pPrefix->d.nRefCnt = 0;
#endif
pPage->d.pLast += sizeof(Prefix) + nAllocSize + sizeof(Postfix);
...
}
}
} while (AllocPage());  // now we should allocate one more page
...
}
...
}

void* MemPool::AllocLarge(int nSize, const char* const szFileName, int nLine, bool bIsArray)
{
...
if (pPrefix)
{
...
pPrefix->d.pPage = NULL;  // indicating it is not allocated from any pages
#if defined USE_SMART_HEAP
if (szFileName) {
strcpy_s(pPrefix->d.szFileName, 31, szFileName); pPrefix->d.szFileName[31] = 0;
} else
pPrefix->d.szFileName[0] = 0;  // null string
pPrefix->d.nLineNumber = nLine;
pPrefix->d.bIsArray = bIsArray;
*((int*) pPrefix->d.szToken) = PREFIX_SIG;
pPrefix->d.nRefCnt = 0;
#endif
...
}
...
}


接下来在Free函数加一段,用来对每个要释放的指针对象作合法性检测。这个无关泄露,但也提一下吧。

void MemPool::Free(void<
4000
/span>* pMem)
{
SYNCHRONIZATION();

#if defined USE_SMART_HEAP
if (!VerifyHeapPointer(pMem)) {
std::cerr << "Memory block bad" << std::endl;
return;
}
#endif
...
}


泄露的检查是在MemPool的析构里做的。这样当程序退出时,会打印泄露信息

MemPool::~MemPool()
{
// Do some cleanups
for (Cleanup *p = m_pCleanups; p != NULL; p = p->pNext)
{
if (p->pHandler)
p->pHandler(p->pParam);
}

#if defined USE_SMART_HEAP
WalkHeap();
#endif
...
}


OK,接下来是泄露检查的实现部分

void MemPool::WalkHeap()
{
if (m_pLarge->d.pNext || !ArePagesAllClear())
{
OutputDebugStringA("Detected memory leak:\n");

char buffer[100];
if (m_pLarge->d.pNext)  // 大内存泄露
{
for (Prefix *pCur = m_pLarge->d.pNext; pCur != NULL; pCur = pCur->d.pNext)
{
if (!VerifyHeapPointer(&pCur[1])) {
OutputDebugStringA("Memory block bad"); break;
}
RenderDesc(pCur, buffer, 100);  // 输出泄露信息
OutputDebugStringA(buffer);
}
}
else  // 页里有泄露
{
for (MemPageHeader *p = m_pFirstPage; p != NULL; p = p->d.pNext)
{
Prefix *pPrefix = (Prefix*) ((char*) p + sizeof(MemPageHeader));
while ((char*) pPrefix < p->d.pLast)  // 遍历页内所有未能回收的内存块
{
if (!IsInBlockPool(pPrefix)) {  // 如果不在任何尺寸的池中
RenderDesc(pPrefix, buffer, 100);  // 输出泄露信息
OutputDebugStringA(buffer);
}
pPrefix = (Prefix*) (pPrefix->d.pPostfix + 1);
}
}
}
}
else
OutputDebugStringA("No memory leak:\n");
}

bool MemPool::ArePagesAllClear()
{
for (MemPageHeader *p = m_pFirstPage; p != NULL; p = p->d.pNext)
{
if (p->d.pLast != (char*) p + sizeof(MemPageHeader))
return false;
}
return true;
}

bool MemPool::VerifyHeapPointer(void* pMem)
{
bool bOk = false;
if (IsPtrOk(pMem))
{
Prefix *pPrefix = (Prefix*) pMem - 1;
if (pPrefix->d.pPostfix->pPrefix == pPrefix)
bOk = true;
}

return (bOk);
}

bool MemPool::IsPtrOk(void* pMem)
{
return ((pMem) && (((INT_PTR) pMem & (ALIGNMENT - 1)) == 0));
}

void MemPool::RenderDesc(Prefix* pPrefix, char* pBuffer, int nBufferLen)
{
sprintf_s(pBuffer, nBufferLen, "%08lx ", (DWORD_PTR) pPrefix);
if (pPrefix->d.szFileName[0])
sprintf_s(pBuffer + strlen(pBuffer), nBufferLen - strlen(pBuffer), "%s %4ld\n", pPrefix->d.szFileName, pPrefix->d.nLineNumber);  // 输出对应文件名和行数
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++内存管理