说明

本文基于链表实现C语言堆内存的检测机制,可检测内存泄露、越界和重复释放等操作问题。

本文仅提供即视代码层面的检测机制,不考虑编译链接级的注入或钩子。此外,该机制暂未考虑并发保护。

相关性文章参见:

C语言通用双向循环链表操作函数集

C语言内存使用的常见问题及解决之道

一  原理

堆内存泄漏检测机制的基本原理是截获对内存分配和释放函数的调用,从而跟踪每块内存的生命周期。例如,每次成功分配一块内存后,将内存分配信息(如指向它的指针、文件名、函数名、行号和申请字节数等)加入一个全局链表中;每当释放一块内存时,再从链表中删除相应的内存信息。这样,当程序结束时,链表中剩余的内存信息结点就对应那些未被释放的内存。

当运行环境支持断点调试时,记录下内存分配的序号会很有用;当运行环境支持堆栈回溯时,可记录回溯信息以便观察泄露时的函数调用栈。此外,可根据使用者指定的文件名和行号信息过滤链表结点,以输出指定内存申请代码处的回溯信息。

对于隐式内存泄露,可在程序运行过程中监控当前内存的总使用量和分配释放情况。以分配内存时的文件名和行号为索引,遍历链表结点即可计算出各处已分配但未释放的内存总量。若在连续多个时间间隔内,某文件中某行所分配的内存总量不断增长,则基本可确定属于隐式内存泄露(尤其是多线程引起的)。

当模块提供动态内存管理的封装接口时,可采用“红区”技术检测内存越界。例如,接口内每次申请比调用者所需更大的内存,将其首尾若干字节(守护字节,Guard Bytes)设置为特殊值,仅将中间部分的内存返回给调用者使用。通过检查特殊字节是否被改写,即可获知是否发生内存越界。其结构示意图如下:

需要注意的是,这些检测机制均会造成目标程序性能上的损失,因此最好能在编译时(通过编译选项)或运行时(通过设置使能标志等)可选地激活。

二  实现

本节将基于《C语言通用双向循环链表操作函数集》一文中的链表接口,实现堆内存检测机制。

文中“OMCI_”和“Omci”前缀为代码所在模块名信息,使用接口时可按需修改这些前缀。

2.1 数据结构

定义内存管理信息结构体如下:

 typedef struct{
const CHAR *pFileName; //内存分配函数调用处所在文件名
const CHAR *pFuncName; //内存分配函数调用处所在函数名
INT32U dwCodeLine; //内存分配函数调用处所在代码行号
INT32U dwMemSize; //内存分配函数申请到的内存字节数
VOID *pvMemAddr; //内存分配函数返回的内存指针地址
}OMCI_MEM_INFO;

这些信息将作为结点数据插入下面的全局链表中:

 T_OMCI_LIST gtMemLeakCheckList = {.pHead=NULL, .pTail=NULL, .dwNodeNum=, .dwNodeDataSize=};

为统计内存分配情况,还定义如下结构体:

 typedef struct{
INT32U dwAllocBytes; //申请内存的总字节数
INT32U dwAllocTimes; //申请内存的总次数
INT32U dwFreeBytes; //申请内存的总字节数
INT32U dwFreeTimes; //释放内存的总次数
}T_MEM_STATIS;

这些统计值将存入下面的全局结构中:

 static T_MEM_STATIS gtMemStatis = {};

2.2 宏代码

为完成基本的内存分配和释放工作,必须调用系统提供的相应库函数(如calloc和free)。但直接使用库函数无法得到文件名等信息,因此需要借助宏对库函数进行封装:

 //动态内存宏,使用方式同系统函数calloc和free,但不可与后者混用
#define OMCI_ALLOC(dwMemSize) OmciAlloc((dwMemSize), __FILE__, FUNC_NAME, __LINE__)
#define OMCI_FREE(pvMemBuf) OmciFree((pvMemBuf), __FILE__, FUNC_NAME, __LINE__)

此处考虑到OmciAlloc/ OmciFree函数可内置内存统计和越界检查,且相比内存泄露检测对系统资源和性能的影响不大,因此未使用条件编译控制。若不需要这些额外的好处,可采用下面的封装方式:

 #ifdef OMCI_MEM_CHECK
#define OMCI_ALLOC(dwMemSize) OmciAlloc((dwMemSize), __FILE__, FUNC_NAME, __LINE__)
#define OMCI_FREE(pvMemBuf) OmciFree((pvMemBuf), __FILE__, FUNC_NAME, __LINE__)
#else
#define OMCI_ALLOC(dwMemSize) calloc((dwMemSize), 1)
#define OMCI_FREE(pvMemBuf) free((pvMemBuf))
#end

OmciAlloc函数内调用calloc库函数来申请内存,OmciFree函数内则调用free库函数来释放内存。使用者在开发时一律使用OMCI_ALLOC和OMCI_FREE宏。注意,OmciAlloc函数未考虑对strdup/strndup/realloc等库函数的替换。

要检测内存越界,需要预留一些守护字节,其长度和内容定义如下:

 //OMCI申请内存时的头守护字节数
#define OMCI_MEM_HEAD_SIZE 8 //OMCI内存管理特征码0xA5(二进制10100101)
#define OMCI_MEM_IDEN_CODE (CHAR)0xA5 //OMCI内存管理特征域所占字节数
#define OMCI_MEM_IDEN_SIZE 4 //OMCI申请内存时的尾守护字节数
#define OMCI_MEM_TAIL_SIZE 2

可见,OMCI_ALLOC在所申请的内存头部预留8个守护字节,其中Byte0~3标记OMCI内存管理特征码0xA5(二进制10100101),前两字节用于越界检测,后两字节用于调用匹配。Byte4~7记录申请的内存长度供OMCI_FREE释放时获取待释放内存大小。此外,所申请的内存尾部预留2个守护字节用于越界检测。守护字节对外屏蔽,即只能使用位于首尾守护字节之间的内存。建议头守护长度为4字节整数倍,以确保可用内存的对齐(否则可能降低CPU处理效率甚至导致访问崩溃)。此外,考虑到库函数分配的内存块通常大于所申请的内存(向上圆整),尾守护字节的开销可以忽略。

若在OMCI_ALLOC中向内存检测链表添加结点以存储待申请内存的信息,在OMCI_FREE中删除指向待删除内存指针所对应的结点,内存检测链表中剩余的结点即对应未释放(疑似泄露)的内存。需要注意的是,若未执行OMCI_ALLOC宏(如直接调用calloc等库函数或使用OMCI_ALLOC宏的分支被跳过),则无法检测到相应的动态内存。

内存泄露检测需要使用链表,对资源和性能影响较大,因此需要使用条件编译控制。同时,对于链表结点的操作比较固定,因此也用宏定义加以封装:

 #ifdef __MEM_LEAK_CHECK
#define OMCI_INIT_MEMINFO() do{ \
OmciInitList(&gtMemLeakCheckList, sizeof(OMCI_MEM_INFO)); \
}while()
#define OMCI_INSERT_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemBufAddr) do{ \
OMCI_MEM_INFO tMemInfo = {}; \
tMemInfo.pFileName = (pFile); \
tMemInfo.pFuncName = (pFunc); \
tMemInfo.dwCodeLine = (dwLine); \
tMemInfo.dwMemSize = (dwMemBytes); \
tMemInfo.pvMemAddr = (pvMemBufAddr); \
OmciAppendListNode(&gtMemLeakCheckList, &tMemInfo); \
}while()
#define OMCI_REMOVE_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemBufAddr) do{ \
OMCI_MEM_INFO tMemInfo = {}; \
tMemInfo.pFileName = (pFile); \
tMemInfo.pFuncName = (pFunc); \
tMemInfo.dwCodeLine = (dwLine); \
tMemInfo.dwMemSize = (dwMemBytes); \
tMemInfo.pvMemAddr = (pvMemBufAddr); \
T_OMCI_LIST_NODE *pDeleteNode = NULL; \
pDeleteNode = OmciLocateListNode(&gtMemLeakCheckList, &tMemInfo, CompareNodeMem); \
OmciRemoveListNode(&gtMemLeakCheckList, pDeleteNode); \
}while()
#define OMCI_REPORT_MEMCHECK() do{ \
OmciCheckMemLeak(&gtMemLeakCheckList); \
}while()
#else
#define OMCI_INIT_MEMINFO()
#define OMCI_INSERT_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemAddr)
#define OMCI_REMOVE_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemAddr)
#define OMCI_REPORT_MEMCHECK()
#endif

条件编译表达式__MEM_LEAK_CHECK(对应Makefile编译选项-D__MEM_LEAK_CHECK)用于控制内存检测功能的开启和关闭。

2.3 函数接口

2.3.1 私有函数

通过链表储存内存分配信息时,链表自身也需要分配和释放内存。若不与外界的普通内存分配和释放区分,将会出现无限循环递归调用。因此,首先对链表函数集稍加改造:

 static T_OMCI_LIST_NODE *CreateListNode(T_OMCI_LIST *pList, VOID *pvNodeData)
{
T_OMCI_LIST_NODE *pInsertNode = NULL;
#ifdef __MEM_LEAK_CHECK //避免内存检测链表创建结点时陷入无限循环
extern T_OMCI_LIST gtMemLeakCheckList;
if(pList == &gtMemLeakCheckList)
{
pInsertNode = (T_OMCI_LIST_NODE*)calloc((sizeof(T_OMCI_LIST_NODE)+pList->dwNodeDataSize), );
}
else
#endif
{
pInsertNode = (T_OMCI_LIST_NODE*)OMCI_ALLOC((sizeof(T_OMCI_LIST_NODE)+pList->dwNodeDataSize));
}
if(NULL == pInsertNode)
{
printf("[%s]pList(%p) failed to alloc for pInsertNode!\n", FUNC_NAME, pList);
return NULL;
} pInsertNode->pvNodeData = (INT8U *)pInsertNode + sizeof(T_OMCI_LIST_NODE);
if(NULL != pvNodeData)
{ //创建非头结点时
memmove(pInsertNode->pvNodeData, pvNodeData, pList->dwNodeDataSize);
} return pInsertNode;
}
static LIST_STATUS RemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
{
OMCI_ISOL_NODE(pNode); //释放链表结点
#ifdef __MEM_LEAK_CHECK //避免内存检测链表销毁结点时陷入无限循环
extern T_OMCI_LIST gtMemLeakCheckList;
if(pList == &gtMemLeakCheckList)
{
free(pNode);
39 }
else
#endif
{
OMCI_FREE(pNode);
} return OMCI_LIST_OK;
}
static LIST_STATUS DestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
{
//释放链表结点
#ifdef __MEM_LEAK_CHECK //避免内存检测链表销毁结点时陷入无限循环
extern T_OMCI_LIST gtMemLeakCheckList;
if(pList == &gtMemLeakCheckList)
{
free(*pNode);
}
else
#endif
{
OMCI_FREE(*pNode);
}
*pNode = NULL; return OMCI_LIST_OK;
}

创建链表结点时判断链表指针是否指向gtMemLeakCheckList。若是,表示检测机制内部申请/释放内存,此时调用calloc/free函数;若否,表示外部使用者申请/释放内存,此时调用OMCI_ALLOC/ OMCI_FREE宏。由此亦知,检测机制不会检测自身的内存泄露。

为隐藏细节和管理方便,首尾守护字节的初始化和解析等操作也封装函数:

 /**********************************************************************
* 函数名称: GenerateMemChecker
* 功能描述: 构造动态内存检测区
* 输入参数: VOID *pMemBuffer :内存管理函数申请到的内存地址
* INT32U dwMemSize :内存管理函数申请到的内存字节数
* 输出参数: NA
* 返 回 值: CHAR *:指向可用动态内存(跳过信息头)的指针
***********************************************************************/
static CHAR *GenerateMemChecker(VOID *pvMemBuf, INT32U dwMemSize)
{
CHAR *pHeader = (CHAR *)pvMemBuf;
memset(pHeader, OMCI_MEM_IDEN_CODE, OMCI_MEM_IDEN_SIZE);
memmove(&pHeader[OMCI_MEM_IDEN_SIZE], &dwMemSize, sizeof(dwMemSize));
memset(&pHeader[OMCI_MEM_HEAD_SIZE+dwMemSize], OMCI_MEM_IDEN_CODE, OMCI_MEM_TAIL_SIZE); return pHeader + OMCI_MEM_HEAD_SIZE;
} /**********************************************************************
* 函数名称: DeriveMemHeader
* 功能描述: 提取动态内存信息头
* 输入参数: VOID *pvMemBuf :内存管理函数申请到的内存地址
* 输出参数: INT32U *dwMemSize :内存管理函数申请到的内存字节数
* 返 回 值: CHAR *:指向动态内存信息头的指针
***********************************************************************/
static CHAR *DeriveMemHeader(VOID *pvMemBuf, INT32U *dwMemSize)
{
CHAR *pHeader = (CHAR *)pvMemBuf - OMCI_MEM_HEAD_SIZE; //若动态内存由OMCI_ALLOC申请,则由信息头获取待释放内存大小
if((OMCI_MEM_IDEN_CODE == pHeader[]) && (OMCI_MEM_IDEN_CODE == pHeader[]))
{
*dwMemSize = *(INT32U*)&(pHeader[OMCI_MEM_IDEN_SIZE]);
} return pHeader;
} /**********************************************************************
* 函数名称: IsMemHeadOverrun/IsMemTailOverrun
* 功能描述: 检查动态内存是否存在头(低地址)/尾(高地址)越界
***********************************************************************/
static BOOL IsMemHeadOverrun(CHAR *pMemHeader)
{
return ((OMCI_MEM_IDEN_CODE != *pMemHeader) || (OMCI_MEM_IDEN_CODE != *(pMemHeader+)));
}
static BOOL IsMemTailOverrun(VOID *pvMemBuf, INT32U dwMemSize)
{
return ((OMCI_MEM_IDEN_CODE != *((CHAR*)pvMemBuf+dwMemSize)) ||
(OMCI_MEM_IDEN_CODE != *((CHAR*)pvMemBuf+dwMemSize+)));
}

内存泄露检测相关的函数如下:

 /**********************************************************************
* 函数名称: PrintListMem
* 功能描述: 打印OMCI内存泄露检测结果
* 输入参数: VOID *pvNodeData :链表结点数据指针
* INT32U dwNodeNum :链表结点数目
***********************************************************************/
static VOID PrintListMem(VOID *pvNodeData, INT32U dwNodeNum)
{
OMCI_MEM_INFO *ptMemInfo = (OMCI_MEM_INFO *)pvNodeData;
printf("[%s(%d)<%s>]%uBytes(Address:%p) were allocated, but unfreed!\n",
ptMemInfo->pFileName, ptMemInfo->dwCodeLine, ptMemInfo->pFuncName,
ptMemInfo->dwMemSize, (CHAR*)ptMemInfo->pvMemAddr);
} /**********************************************************************
* 函数名称: OmciCheckMemLeak
* 功能描述: OMCI内存泄露检测
* 输入参数: T_OMCI_LIST *gpMemLeakCheckList :泄露结点链表指针
***********************************************************************/
static VOID OmciCheckMemLeak(T_OMCI_LIST *gpMemLeakCheckList)
{
INT32U dwMemLeakNum = OmciGetListNodeNum(gpMemLeakCheckList);
if( == dwMemLeakNum)
{
printf("No Memory Leakage Detected!\n");
return;
}
printf("Suspected Memory Leakage Occurrence(%u Time(s)):\n", dwMemLeakNum);
OmciPrintListNode(gpMemLeakCheckList, PrintListMem);
} /**********************************************************************
* 函数名称: CompareNodeMem
* 功能描述: 可疑泄露结点信息比较
* 输入参数: VOID *pNodeData :链表结点数据指针
* VOID *pData :待比较外部数据指针
* INT32U dwNodeDataSize :链表结点数据大小
* 输出参数: NA
* 返 回 值: 0:Equal; !0:Unequal
* 注意事项: 比较长度为结点数据字节数,即默认与外部数据大小一致
***********************************************************************/
static INT8U CompareNodeMem(VOID *pNodeData, VOID *pData, INT32U dwNodeDataSize)
{
return (((OMCI_MEM_INFO *)pNodeData)->pvMemAddr != ((OMCI_MEM_INFO *)pData)->pvMemAddr);
}

CompareNodeMem函数中,内存指针匹配时内存大小不一定匹配,此时仍可正常释放,但头守护字节的内存大小字段被越界改写。若要检测这种错误(意义不大),可将函数改写为:

 INT8U CompareNodeMem(VOID *pNodeData, VOID *pData, INT32U dwNodeDataSize)
{
OMCI_MEM_INFO *ptMemInfo1 = (OMCI_MEM_INFO *)pNodeData;
OMCI_MEM_INFO *ptMemInfo2 = (OMCI_MEM_INFO *)pData; if(ptMemInfo1->pvMemBufAddr != ptMemInfo2->pvMemBufAddr)
return ; if(ptMemInfo1->dwMemSize != ptMemInfo2->dwMemSize)
{ //因内存指针匹配,故仍可正常释放,但提示某处可能存在内存越界
printf("MemSize Field of Head Guard was Overwritten([%s(%u)<%s>]Addr:%p, Size:%u->%u)!\n",
ptMemInfo2->pFileName, ptMemInfo2->dwCodeLine, ptMemInfo2->pFuncName,
ptMemInfo2->pvMemBufAddr, ptMemInfo1->dwMemSize, ptMemInfo2->dwMemSize);
} return ;
}

基于上述私有函数,可进一步构建内存管理和检测的基本接口。

2.3.2 公共接口

若要启用内存泄露检测机制,则分配内存前必须先初始化全局链表gtMemLeakCheckList:

 /**********************************************************************
* 函数名称: OmciInitMemCheck
* 功能描述: 初始化内存泄露检测机制
* 注意事项: 检测内存泄露时必须先调用本函数进行初始化,然后分配内存
***********************************************************************/
VOID OmciInitMemCheck(VOID)
{
OMCI_INIT_MEMINFO();
}

完成链表初始化工作后,内存分配接口内就可向其插入内存信息:

 /**********************************************************************
* 函数名称: OmciAlloc
* 功能描述: 动态分配内存并初始化
* 输入参数: INT32U dwMemSize :内存管理函数申请到的内存字节数
* const CHAR *pFileName :内存管理函数调用处所在文件名
* const CHAR *pFuncName :内存管理函数调用处所在函数名
* INT32U dwCodeLine :内存管理函数调用处所在代码行号
* 输出参数: NA
* 返 回 值: VOID *:内存管理函数返回的内存指针地址
* 注意事项: 必须通过OMCI_ALLOC宏间接调用,且不可与free配对使用
***********************************************************************/
VOID *OmciAlloc(INT32U dwMemSize, const CHAR *pFileName, const CHAR *pFuncName, INT32U dwCodeLine)
{
if( == dwMemSize)
{
printf("[%s(%u)<%s>] Memmory to be allocated must be larger than Zero Byte!\n",
pFileName, dwCodeLine, pFuncName);
return NULL;
} VOID *pvMemBuf = calloc((dwMemSize+OMCI_MEM_HEAD_SIZE+OMCI_MEM_TAIL_SIZE), );
if(NULL == pvMemBuf)
{
printf("[%s(%d)<%s>] Allocating %uBytes failed, no memory available!\n",
pFileName, dwCodeLine, pFuncName, dwMemSize);
return NULL;
} gtMemStatis.dwAllocBytes += dwMemSize;
gtMemStatis.dwAllocTimes++; pvMemBuf = GenerateMemChecker(pvMemBuf, dwMemSize);
OMCI_INSERT_MEMINFO(pFileName, pFuncName, dwCodeLine, dwMemSize, pvMemBuf); return pvMemBuf;
}

通过头尾守护字节和内存分配信息,释放时可检测内存越界并删除链表结点:

 /**********************************************************************
* 函数名称: OmciFree
* 功能描述: 释放通过OMCI_ALLOC分配的动态内存
* 输入参数: VOID* pvMemBuf :指向待释放内存的指针
* const CHAR *pFileName :内存管理函数调用处所在文件名
* const CHAR *pFuncName :内存管理函数调用处所在函数名
* INT32U dwCodeLine :内存管理函数调用处所在代码行号
* 输出参数: NA
* 返 回 值: FUNC_STATUS
* 注意事项: 必须通过OMCI_FREE宏间接调用,且不可与malloc/calloc等配对使用
***********************************************************************/
FUNC_STATUS OmciFree(VOID* pvMemBuf, const CHAR* pFileName, const CHAR* pFuncName, INT32U dwCodeLine)
{
if(NULL == pvMemBuf)
{
printf("[%s(%d)<%s>] Cannot free Null Address!\n", pFileName, dwCodeLine, pFuncName);
return S_NULL_POINTER;
} INT32U dwMemSize = ;
CHAR *pMemHeader = DeriveMemHeader(pvMemBuf, &dwMemSize); //对于Double Free,无论头守护字节是否已被改写,free均会发生段错误;
//对于非OMCI_ALLOC申请的内存,free可能造成越界释放。
//但本函数难以区分两种情况。综合考虑,决定拦截并警告。
if( == dwMemSize)
{
printf("Warning: [%s(%d)<%s>]Free Memory(%p) that was 1) not allocated by OMCI_ALLOC or 2) freed by OMCI_FREE;\n",
pFileName, dwCodeLine, pFuncName, pvMemBuf);
printf(" Both cases are critical, and NO free action takes place to minimize the casualties!!!\n");
return S_ILLEGAL_PARAM;
} if(IsMemHeadOverrun(pMemHeader))
{
printf("Warning: [%s(%d)<%s>]Overrun Occurs at Lower Address(%p);\n",
pFileName, dwCodeLine, pFuncName, pvMemBuf);
}
if(IsMemTailOverrun(pvMemBuf, dwMemSize))
{
printf("Warning: [%s(%d)<%s>]Overrun Occurs at Higher Address(%p);\n",
pFileName, dwCodeLine, pFuncName, pvMemBuf);
} OMCI_REMOVE_MEMINFO(pFileName, pFuncName, dwCodeLine, dwMemSize, pvMemBuf); free(pMemHeader); gtMemStatis.dwFreeBytes += dwMemSize;
gtMemStatis.dwFreeTimes++; return S_OK;
}

程序执行过程中,可随时调用OmciShowMemInfo查看内存分配的统计信息及泄露情况:

 /**********************************************************************
* 函数名称: OmciShowMemInfo
* 功能描述: 查看OMCI模块当前内存申请与释放信息
* 输入参数: NA
* 输出参数: NA
* 返 回 值: VOID
* 注意事项: 当__MEM_LEAK_CHECK已定义时,本函数同时显示可能存在的内存泄露
***********************************************************************/
VOID OmciShowMemInfo(VOID)
{
printf("-------------------Omci Memory Info-------------------\n");
printf("%-15s%-15s%-15s%-15s%-15s\n", "AllocBytes", "AllocTimes", "FreeBytes", "FreeTimes", "<UnFreeBytes>");
printf("%-15u%-15u%-15u%-15u%-15u\n", gtMemStatis.dwAllocBytes, gtMemStatis.dwAllocTimes,
gtMemStatis.dwFreeBytes, gtMemStatis.dwFreeTimes, gtMemStatis.dwAllocBytes-gtMemStatis.dwFreeBytes); OMCI_REPORT_MEMCHECK();
}

三  测试

本节将对上文实现的堆内存检测机制进行测试。

 CHAR *gpGlobalMem = NULL;
VOID GlobalAllocTest(VOID){
gpGlobalMem = OMCI_ALLOC();
}
VOID GlobalFreeTest(VOID){
OMCI_FREE(gpGlobalMem);
} INT32S main(VOID)
{
INT8U ucTestIndex = ;
OMCI_INIT_MEMINFO(); printf("\n<Test Case %u>: Simple Alloc-Free within Function!\n", ucTestIndex++);
CHAR *ptr = OMCI_ALLOC();
CHAR *ptr1 = OMCI_ALLOC();
CHAR *ptr2 = OMCI_ALLOC();
CHAR *ptr3 = OMCI_ALLOC();
strcpy(ptr, "");
printf("ptr=%s(PreFree)\n", ptr);
OMCI_FREE(ptr); //为测试需要,此处释放内存后不置ptr为NULL
printf("ptr=%s(PostFree)\n", ptr);
OmciShowMemInfo(); printf("\n<Test Case %u>: Simple Alloc-Free outside Function!\n", ucTestIndex++);
GlobalAllocTest();
OmciShowMemInfo(); printf("\nTest Case %u: Free all Allocated Memories!\n", ucTestIndex++);
OMCI_FREE(ptr1);
OMCI_FREE(ptr2);
OMCI_FREE(ptr3);
GlobalFreeTest();
OmciShowMemInfo(); printf("\nTest Case %u: Overrun Downward!\n", ucTestIndex++);
CHAR *ptr5 = OMCI_ALLOC();
*(ptr5-) = ;
OMCI_FREE(ptr5); printf("\nTest Case %u: Overrun Upward!\n", ucTestIndex++);
CHAR *ptr6 = OMCI_ALLOC();
strcpy(ptr6, "");
OMCI_FREE(ptr6); printf("\nTest Case %u: Doubly Free!\n", ucTestIndex++);
OMCI_FREE(ptr6); printf("\nTest Case %u: Free Memory not allocated by OMCI_ALLOC!\n", ucTestIndex++);
CHAR *ptr7 = malloc();
OMCI_FREE(ptr7); return ;
}

关闭-D__MEM_LEAK_CHECK编译选项时,测试结果如下所示(不检查堆内存泄露):

 <Test Case >: Simple Alloc-Free within Function!
ptr=(PreFree)
ptr=(PostFree)
-------------------Omci Memory Info-------------------
AllocBytes AllocTimes FreeBytes FreeTimes <UnFreeBytes> <Test Case >: Simple Alloc-Free outside Function!
-------------------Omci Memory Info-------------------
AllocBytes AllocTimes FreeBytes FreeTimes <UnFreeBytes> Test Case : Free all Allocated Memories!
-------------------Omci Memory Info-------------------
AllocBytes AllocTimes FreeBytes FreeTimes <UnFreeBytes> Test Case : Overrun Downward!
Warning: [Omci_Main.c()<main>]Overrun Occurs at Lower Address(0x8e38050); Test Case : Overrun Upward!
Warning: [Omci_Main.c()<main>]Overrun Occurs at Higher Address(0x8e38010); Test Case : Doubly Free!
Warning: [Omci_Main.c()<main>]Free Memory(0x8e38010) that was ) not allocated by OMCI_ALLOC or ) freed by OMCI_FREE;
Both cases are critical, and NO free action takes place to minimize the casualties!!! Test Case : Free Memory Not Allocated by OMCI_ALLOC!
Warning: [Omci_Main.c()<main>]Free Memory(0x8e38048) that was ) not allocated by OMCI_ALLOC or ) freed by OMCI_FREE;
Both cases are critical, and NO free action takes place to minimize the casualties!!!

开启-D__MEM_LEAK_CHECK编译选项时,测试结果如下所示:

 <Test Case >: Simple Alloc-Free within Function!
ptr=(PreFree)
ptr=(PostFree)
-------------------Omci Memory Info-------------------
AllocBytes AllocTimes FreeBytes FreeTimes <UnFreeBytes> Suspected Memory Leakage Occurrence( Time(s)):
[Omci_Main.c()<main>]20Bytes(Address:0x8428078) were allocated, but unfreed!
[Omci_Main.c()<main>]30Bytes(Address:0x84280c8) were allocated, but unfreed!
[Omci_Main.c()<main>]40Bytes(Address:0x8428120) were allocated, but unfreed! <Test Case >: Simple Alloc-Free outside Function!
-------------------Omci Memory Info-------------------
AllocBytes AllocTimes FreeBytes FreeTimes <UnFreeBytes> Suspected Memory Leakage Occurrence( Time(s)):
[Omci_Main.c()<main>]20Bytes(Address:0x8428078) were allocated, but unfreed!
[Omci_Main.c()<main>]30Bytes(Address:0x84280c8) were allocated, but unfreed!
[Omci_Main.c()<main>]40Bytes(Address:0x8428120) were allocated, but unfreed!
[Omci_Main.c()<GlobalAllocTest>]50Bytes(Address:0x8428180) were allocated, but unfreed! Test Case : Free all Allocated Memories!
-------------------Omci Memory Info-------------------
AllocBytes AllocTimes FreeBytes FreeTimes <UnFreeBytes> No Memory Leakage Detected! Test Case : Overrun Downward!
Warning: [Omci_Main.c()<main>]Overrun Occurs at Lower Address(0x84280c8); Test Case : Overrun Upward!
Warning: [Omci_Main.c()<main>]Overrun Occurs at Higher Address(0x8428038); Test Case : Doubly Free!
Warning: [Omci_Main.c()<main>]Free Memory(0x8428038) that was ) not allocated by OMCI_ALLOC or ) freed by OMCI_FREE;
Both cases are critical, and NO free action takes place to minimize the casualties!!! Test Case : Free Memory Not Allocated by OMCI_ALLOC!
Warning: [Omci_Main.c()<main>]Free Memory(0x84280c0) that was ) not allocated by OMCI_ALLOC or ) freed by OMCI_FREE;
Both cases are critical, and NO free action takes place to minimize the casualties!!!

四  后记

在大型项目中,系统支撑层面通常会封装统一的内存申请/释放接口,以便管理和优化。发生内存泄露时,外部检测工具所报告的泄露代码位于接口内,对排障帮助有限(除非提供堆栈回溯)。而本文提供的检测机制天然地寄生于管理接口之上,能准确地指示泄露内存的分配现场。

若要直接接管malloc/free等库函数调用,可采用如下方式:

 //Omci_Alloc.h
#ifndef OMCI_INNER_ALLOC
#define malloc(dwMemSize) OmciAlloc((dwMemSize), __FILE__, FUNC_NAME, __LINE__)
#define free(pvMemBuf) OmciFree((pvMemBuf), __FILE__, FUNC_NAME, __LINE__)
#endif //Omci_Alloc.c
#define OMCI_INNER_ALLOC
#include "Omci_Alloc.h"

如此可避免OmciAlloc/ OmciFree内递归调用库函数。若在Omci_Alloc.c内实现内存泄露检测的链表操作(单链表即可),则不必“污染”双向循环链表函数集。

此外,为使检测代码的简单易懂,文中未对其进行优化和异常保护。

基于链表的C语言堆内存检测的更多相关文章

  1. C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针

    C语言堆内存管理上出现的问题,内存泄露,野指针使用,非法释放指针 (1)开辟的内存没有释放,造成内存泄露 (2)野指针被使用或释放 (3)非法释放指针 (1)开辟的内存没有释放.造成内存泄露,以下的样 ...

  2. 基于Java软引用机制最大使用JVM堆内存并杜绝OutOfMemory

    题记:说好的坚持一周两篇文章在无数琐事和自己的懒惰下没有做好,在此表达一下对自己的不满并对有严格执行力的人深表敬意!!!! -------------------------------------- ...

  3. Linux堆内存管理深入分析(下)

     Linux堆内存管理深入分析 (下半部) 作者@走位,阿里聚安全 0 前言回顾 在上一篇文章中(链接见文章底部),详细介绍了堆内存管理中涉及到的基本概念以及相互关系,同时也着重介绍了堆中chunk分 ...

  4. 不可或缺 Windows Native (9) - C 语言: 动态分配内存,链表,位域

    [源码下载] 不可或缺 Windows Native (9) - C 语言: 动态分配内存,链表,位域 作者:webabcd 介绍不可或缺 Windows Native 之 C 语言 动态分配内存 链 ...

  5. (十一)C语言中内存堆和栈的区别

    在计算机领域,堆栈是一个不容忽视的概念,我们编写的C语言程序基本上都要用到.但对于很多的初学着来说,堆栈是一个很模糊的概念. 堆栈:一种数据结构.一个在程序运行时用于存放的地方,这可能是很多初学者的认 ...

  6. C语言中堆内存的开辟和释放与内存处理函数

    C语言动态分配内存,malloc的出现就是来弥补静态内存分配的缺点 比如说我们在定义数组的时候,数组的长度必须是一个常量,不能改变的值,假如我事先定义了数组,一旦业务需求发生改变,那么这个数组就不能再 ...

  7. jconsole工具检测堆内存变化的使用

    jconsole将Java写的程序检测. 从Java 5开始 引入了 JConsole.JConsole 是一个内置 Java 性能分析器,可以从命令行或在 GUI shell 中运行.您可以轻松地使 ...

  8. 栈 堆 stack heap 堆内存 栈内存 内存分配中的堆和栈 掌握堆内存的权柄就是返回的指针 栈是面向线程的而堆是面向进程的。 new/delete and malloc/ free 指针与内存模型

    小结: 1.栈内存 为什么快? Due to this nature, the process of storing and retrieving data from the stack is ver ...

  9. C语言中内存分配 (转)

    在任何程序设计环境及语言中,内存管理都十分重要.在目前的计算机系统或嵌入式系统中,内存资源仍然是有限的.因此在程序设计中,有效地管理内存资源是程序员首先考虑的问题. 第1节主要介绍内存管理基本概念,重 ...

随机推荐

  1. SpringMVC系列(十一)把后台返回的数据转换成json、文件下载、文件上传

    一.后台返回的数据转换成json 1.引入转换json需要的3个依赖 <!--json转换需要的依赖 begin --> <dependency> <groupId> ...

  2. Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding.【转】

    今天碰到了一个查询异常问题,上网查了一下,感谢原创和译者 如果你使用的数据库连接类是 the Data Access Application Blocks "SqlHelper" ...

  3. Android多任务切换与Activity启动模式SingleTask之间关系的分析

    这里会以多个场景列子进行分析,在分析之前先了解一下基本的概念. Task任务:一系列Activity的集合,这些Activity以栈的形式进行排列(后进先出). 那在什么时候系统会新建一个Task任务 ...

  4. Python——signal

    该模块为在Python中使用信号处理句柄提供支持.下面是一些使用信号和他们的句柄时需要注意的事项: 除了信号 SIGCHLD 的句柄遵从底层的实现外,专门针对一个信号的句柄一旦设置,除非被明确地重置, ...

  5. php 统计fasta 序列长度和GC含量

    最近php7的消息铺天盖地, 忍不住想尝试下.星期天看了下语法, 写个小脚本练下手: 这个脚本读取fasta 文件, 输出序列的长度和GC含量: <?php $fasta = "tes ...

  6. linux 上安裝lnmp

    1.確保有一台服務器可以正常運行 2.熟練知道一些基本的命令 3.這裡我以lnmp集成環境為例 https://lnmp.org/install.html 4.安裝大約30分鐘左右 5.安裝完畢,訪問 ...

  7. UNIX环境编程学习笔记(3)——文件I/O之内核 I/O 数据结构

    lienhua342014-08-27 内核使用三种数据结构表示打开的文件,分别是文件描述符表.文件表和 V 节点表. (1) 每个进程在进程表中都有一个记录项,记录项中包含有一张打开文件描述符表,每 ...

  8. 小程序实现textarea随输入的文字行数变化高度自动增加

    参考链接:https://blog.csdn.net/liuwengai/article/details/78987957 该实现方法是根据上面的链接改编为小程序的实现,代码如下: wxml: < ...

  9. 超强OCR文字识别软件首选ABBYY FineReader

    提到纸质文档—转换—文本格式—可编辑这些字眼,相信大家的第一反映都是OCR文字识别软件,如何排除错误或利用辅助信息提高识别正确率,是OCR最重要的课题,衡量一个OCR系统性能好坏的主要指标无非是精确度 ...

  10. javascript关于onclick()

    1 <html> <head> <title>js1 </title> <style> #content{ margin:0 auto; t ...