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

本文详细介绍了内存泄露的检测与管理方法,包括自动查找内存泄露的功能实现,通过添加记录文件名、行号、引用计数等信息来追踪内存分配与释放情况。文中还涉及到内存池的使用,以及在内存申请、释放时的合法性检测与泄露检查,最后在程序退出时进行泄露信息的打印。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这章是加入内存泄露的自动查找的。之前已经发布了,优快云不知道给整哪去了,我自己也没存档。懒得再仔细重写了,随便写一下。

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

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

    _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* 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);  // 输出对应文件名和行数
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值