原文链接
返回主页
clover_toeic
博客园首页联系管理随笔- 91 文章- 0 评论- 173 阅读- 90万
基于链表的C语言堆内存检测
说明
本文基于链表实现C语言堆内存的检测机制,可检测内存泄露、越界和重复释放等操作问题。
本文仅提供即视代码层面的检测机制,不考虑编译链接级的注入或钩子。此外,该机制暂未考虑并发保护。
相关性文章参见:
《C语言通用双向循环链表操作函数集》
《C语言内存使用的常见问题及解决之道》
一 原理
堆内存泄漏检测机制的基本原理是截获对内存分配和释放函数的调用,从而跟踪每块内存的生命周期。例如,每次成功分配一块内存后,将内存分配信息(如指向它的指针、文件名、函数名、行号和申请字节数等)加入一个全局链表中;每当释放一块内存时,再从链表中删除相应的内存信息。这样,当程序结束时,链表中剩余的内存信息结点就对应那些未被释放的内存。
当运行环境支持断点调试时,记录下内存分配的序号会很有用;当运行环境支持堆栈回溯时,可记录回溯信息以便观察泄露时的函数调用栈。此外,可根据使用者指定的文件名和行号信息过滤链表结点,以输出指定内存申请代码处的回溯信息。
对于隐式内存泄露,可在程序运行过程中监控当前内存的总使用量和分配释放情况。以分配内存时的文件名和行号为索引,遍历链表结点即可计算出各处已分配但未释放的内存总量。若在连续多个时间间隔内,某文件中某行所分配的内存总量不断增长,则基本可确定属于隐式内存泄露(尤其是多线程引起的)。
当模块提供动态内存管理的封装接口时,可采用“红区”技术检测内存越界。例如,接口内每次申请比调用者所需更大的内存,将其首尾若干字节(守护字节,Guard Bytes)设置为特殊值,仅将中间部分的内存返回给调用者使用。通过检查特殊字节是否被改写,即可获知是否发生内存越界。其结构示意图如下:
需要注意的是,这些检测机制均会造成目标程序性能上的损失,因此最好能在编译时(通过编译选项)或运行时(通过设置使能标志等)可选地激活。
二 实现
本节将基于《C语言通用双向循环链表操作函数集》一文中的链表接口,实现堆内存检测机制。
文中“OMCI_”和“Omci”前缀为代码所在模块名信息,使用接口时可按需修改这些前缀。
2.1 数据结构
定义内存管理信息结构体如下:
复制代码
1 typedef struct{
2 const CHAR *pFileName; //内存分配函数调用处所在文件名
3 const CHAR *pFuncName; //内存分配函数调用处所在函数名
4 INT32U dwCodeLine; //内存分配函数调用处所在代码行号
5 INT32U dwMemSize; //内存分配函数申请到的内存字节数
6 VOID *pvMemAddr; //内存分配函数返回的内存指针地址
7 }OMCI_MEM_INFO;
复制代码
这些信息将作为结点数据插入下面的全局链表中:
1 T_OMCI_LIST gtMemLeakCheckList = {.pHead=NULL, .pTail=NULL, .dwNodeNum=0, .dwNodeDataSize=0};
为统计内存分配情况,还定义如下结构体:
1 typedef struct{
2 INT32U dwAllocBytes; //申请内存的总字节数
3 INT32U dwAllocTimes; //申请内存的总次数
4 INT32U dwFreeBytes; //申请内存的总字节数
5 INT32U dwFreeTimes; //释放内存的总次数
6 }T_MEM_STATIS;
这些统计值将存入下面的全局结构中:
1 static T_MEM_STATIS gtMemStatis = {0};
2.2 宏代码
为完成基本的内存分配和释放工作,必须调用系统提供的相应库函数(如calloc和free)。但直接使用库函数无法得到文件名等信息,因此需要借助宏对库函数进行封装:
1 //动态内存宏,使用方式同系统函数calloc和free,但不可与后者混用
2 #define OMCI_ALLOC(dwMemSize) OmciAlloc((dwMemSize), FILE, FUNC_NAME, LINE)
3 #define OMCI_FREE(pvMemBuf) OmciFree((pvMemBuf), FILE, FUNC_NAME, LINE)
此处考虑到OmciAlloc/ OmciFree函数可内置内存统计和越界检查,且相比内存泄露检测对系统资源和性能的影响不大,因此未使用条件编译控制。若不需要这些额外的好处,可采用下面的封装方式:
复制代码
1 #ifdef OMCI_MEM_CHECK
2 #define OMCI_ALLOC(dwMemSize) OmciAlloc((dwMemSize), FILE, FUNC_NAME, LINE)
3 #define OMCI_FREE(pvMemBuf) OmciFree((pvMemBuf), FILE, FUNC_NAME, LINE)
4 #else
5 #define OMCI_ALLOC(dwMemSize) calloc((dwMemSize), 1)
6 #define OMCI_FREE(pvMemBuf) free((pvMemBuf))
7 #end
复制代码
OmciAlloc函数内调用calloc库函数来申请内存,OmciFree函数内则调用free库函数来释放内存。使用者在开发时一律使用OMCI_ALLOC和OMCI_FREE宏。注意,OmciAlloc函数未考虑对strdup/strndup/realloc等库函数的替换。
要检测内存越界,需要预留一些守护字节,其长度和内容定义如下:
复制代码
1 //OMCI申请内存时的头守护字节数
2 #define OMCI_MEM_HEAD_SIZE 8
3
4 //OMCI内存管理特征码0xA5(二进制10100101)
5 #define OMCI_MEM_IDEN_CODE (CHAR)0xA5
6
7 //OMCI内存管理特征域所占字节数
8 #define OMCI_MEM_IDEN_SIZE 4
9
10 //OMCI申请内存时的尾守护字节数
11 #define OMCI_MEM_TAIL_SIZE 2
复制代码
可见,OMCI_ALLOC在所申请的内存头部预留8个守护字节,其中Byte03标记OMCI内存管理特征码0xA5(二进制10100101),前两字节用于越界检测,后两字节用于调用匹配。Byte47记录申请的内存长度供OMCI_FREE释放时获取待释放内存大小。此外,所申请的内存尾部预留2个守护字节用于越界检测。守护字节对外屏蔽,即只能使用位于首尾守护字节之间的内存。建议头守护长度为4字节整数倍,以确保可用内存的对齐(否则可能降低CPU处理效率甚至导致访问崩溃)。此外,考虑到库函数分配的内存块通常大于所申请的内存(向上圆整),尾守护字节的开销可以忽略。
若在OMCI_ALLOC中向内存检测链表添加结点以存储待申请内存的信息,在OMCI_FREE中删除指向待删除内存指针所对应的结点,内存检测链表中剩余的结点即对应未释放(疑似泄露)的内存。需要注意的是,若未执行OMCI_ALLOC宏(如直接调用calloc等库函数或使用OMCI_ALLOC宏的分支被跳过),则无法检测到相应的动态内存。
内存泄露检测需要使用链表,对资源和性能影响较大,因此需要使用条件编译控制。同时,对于链表结点的操作比较固定,因此也用宏定义加以封装:
复制代码
1 #ifdef __MEM_LEAK_CHECK
2 #define OMCI_INIT_MEMINFO() do{
3 OmciInitList(>MemLeakCheckList, sizeof(OMCI_MEM_INFO));
4 }while(0)
5 #define OMCI_INSERT_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemBufAddr) do{
6 OMCI_MEM_INFO tMemInfo = {0};
7 tMemInfo.pFileName = (pFile);
8 tMemInfo.pFuncName = (pFunc);
9 tMemInfo.dwCodeLine = (dwLine);
10 tMemInfo.dwMemSize = (dwMemBytes);
11 tMemInfo.pvMemAddr = (pvMemBufAddr);
12 OmciAppendListNode(>MemLeakCheckList, &tMemInfo);
13 }while(0)
14 #define OMCI_REMOVE_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemBufAddr) do{
15 OMCI_MEM_INFO tMemInfo = {0};
16 tMemInfo.pFileName = (pFile);
17 tMemInfo.pFuncName = (pFunc);
18 tMemInfo.dwCodeLine = (dwLine);
19 tMemInfo.dwMemSize = (dwMemBytes);
20 tMemInfo.pvMemAddr = (pvMemBufAddr);
21 T_OMCI_LIST_NODE *pDeleteNode = NULL;
22 pDeleteNode = OmciLocateListNode(>MemLeakCheckList, &tMemInfo, CompareNodeMem);
23 OmciRemoveListNode(>MemLeakCheckList, pDeleteNode);
24 }while(0)
25 #define OMCI_REPORT_MEMCHECK() do{
26 OmciCheckMemLeak(>MemLeakCheckList);
27 }while(0)
28 #else
29 #define OMCI_INIT_MEMINFO()
30 #define OMCI_INSERT_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemAddr)
31 #define OMCI_REMOVE_MEMINFO(pFile, pFunc, dwLine, dwMemBytes, pvMemAddr)
32 #define OMCI_REPORT_MEMCHECK()
33 #endif
复制代码
条件编译表达式__MEM_LEAK_CHECK(对应Makefile编译选项-D__MEM_LEAK_CHECK)用于控制内存检测功能的开启和关闭。
2.3 函数接口
2.3.1 私有函数
通过链表储存内存分配信息时,链表自身也需要分配和释放内存。若不与外界的普通内存分配和释放区分,将会出现无限循环递归调用。因此,首先对链表函数集稍加改造:
复制代码
1 static T_OMCI_LIST_NODE *CreateListNode(T_OMCI_LIST *pList, VOID pvNodeData)
2 {
3 T_OMCI_LIST_NODE pInsertNode = NULL;
4 #ifdef __MEM_LEAK_CHECK //避免内存检测链表创建结点时陷入无限循环
5 extern T_OMCI_LIST gtMemLeakCheckList;
6 if(pList == >MemLeakCheckList)
7 {
8 pInsertNode = (T_OMCI_LIST_NODE)calloc((sizeof(T_OMCI_LIST_NODE)+pList->dwNodeDataSize), 1);
9 }
10 else
11 #endif
12 {
13 pInsertNode = (T_OMCI_LIST_NODE)OMCI_ALLOC((sizeof(T_OMCI_LIST_NODE)+pList->dwNodeDataSize));
14 }
15 if(NULL == pInsertNode)
16 {
17 printf("[%s]pList(%p) failed to alloc for pInsertNode!\n", FUNC_NAME, pList);
18 return NULL;
19 }
20
21 pInsertNode->pvNodeData = (INT8U *)pInsertNode + sizeof(T_OMCI_LIST_NODE);
22 if(NULL != pvNodeData)
23 { //创建非头结点时
24 memmove(pInsertNode->pvNodeData, pvNodeData, pList->dwNodeDataSize);
25 }
26
27 return pInsertNode;
28 }
29 static LIST_STATUS RemoveListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE *pNode)
30 {
31 OMCI_ISOL_NODE(pNode);
32
33 //释放链表结点
34 #ifdef __MEM_LEAK_CHECK //避免内存检测链表销毁结点时陷入无限循环
35 extern T_OMCI_LIST gtMemLeakCheckList;
36 if(pList == >MemLeakCheckList)
37 {
38 free(pNode);
39 }
40 else
41 #endif
42 {
43 OMCI_FREE(pNode);
44 }
45
46 return OMCI_LIST_OK;
47 }
48 static LIST_STATUS DestroyListNode(T_OMCI_LIST *pList, T_OMCI_LIST_NODE **pNode)
49 {
50 //释放链表结点
51 #ifdef __MEM_LEAK_CHECK //避免内存检测链表销毁结点时陷入无限循环
52 extern T_OMCI_LIST gtMemLeakCheckList;
53 if(pList == >MemLeakCheckList)
54 {
55 free(*pNode);
56 }
57 else
58 #endif
59 {
60 OMCI_FREE(*pNode);
61 }
62 *pNode = NULL;
63
64 return OMCI_LIST_OK;
65 }
复制代码
创建链表结点时判断链表指针是否指向gtMemLeakCheckList。若是,表示检测机制内部申请/释放内存,此时调用calloc/free函数;若否,表示外部使用者申请/释放内存,此时调用OMCI_ALLOC/ OMCI_FREE宏。由此亦知,检测机制不会检测自身的内存泄露。
为隐藏细节和管理方便,首尾守护字节的初始化和解析等操作也封装函数:
复制代码
1 /**********************************************************************
2 * 函数名称: GenerateMemChecker
3 * 功能描述: 构造动态内存检测区
4 * 输入参数: VOID *pMemBuffer :内存管理函数申请到的内存地址
5 * INT32U dwMemSize :内存管理函数申请到的内存字节数
6 * 输出参数: NA
7 * 返 回 值: CHAR *:指向可用动态内存(跳过信息头)的指针
8 *****/
9 static CHAR GenerateMemChecker(VOID pvMemBuf, INT32U dwMemSize)
10 {
11 CHAR pHeader = (CHAR )pvMemBuf;
12 memset(pHeader, OMCI_MEM_IDEN_CODE, OMCI_MEM_IDEN_SIZE);
13 memmove(&pHeader[OMCI_MEM_IDEN_SIZE], &dwMemSize, sizeof(dwMemSize));
14 memset(&pHeader[OMCI_MEM_HEAD_SIZE+dwMemSize], OMCI_MEM_IDEN_CODE, OMCI_MEM_TAIL_SIZE);
15
16 return pHeader + OMCI_MEM_HEAD_SIZE;
17 }
18
19 /
20 * 函数名称: DeriveMemHeader
21 * 功能描述: 提取动态内存信息头
22 * 输入参数: VOID *pvMemBuf :内存管理函数申请到的内存地址
23 * 输出参数: INT32U *dwMemSize :内存管理函数申请到的内存字节数
24 * 返 回 值: CHAR *:指向动态内存信息头的指针
25 *******/
26 static CHAR DeriveMemHeader(VOID pvMemBuf, INT32U dwMemSize)
27 {
28 CHAR pHeader = (CHAR )pvMemBuf - OMCI_MEM_HEAD_SIZE;
29
30 //若动态内存由OMCI_ALLOC申请,则由信息头获取待释放内存大小
31 if((OMCI_MEM_IDEN_CODE == pHeader[2]) && (OMCI_MEM_IDEN_CODE == pHeader[3]))
32 {
33 dwMemSize = (INT32U)&(pHeader[OMCI_MEM_IDEN_SIZE]);
34 }
35
36 return pHeader;
37 }
38
39 /
40 * 函数名称: IsMemHeadOverrun/IsMemTailOverrun
41 * 功能描述: 检查动态内存是否存在头(低地址)/尾(高地址)越界
42 ***********************************************************************/
43 static BOOL IsMemHeadOverrun(CHAR *pMemHeader)
44 {
45 return ((OMCI_MEM_IDEN_CODE != *pMemHeader) || (OMCI_MEM_IDEN_CODE != *(pMemHeader+1)));
46 }
47 static BOOL IsMemTailOverrun(VOID *pvMemBuf, INT32U dwMemSize)
48 {
49 return ((OMCI_MEM_IDEN_CODE != ((CHAR)pvMemBuf+dwMemSize)) ||
50 (OMCI_MEM_IDEN_CODE != ((CHAR)pvMemBuf+dwMemSize+1)));
51 }
复制代码
内存泄露检测相关的函数如下:
复制代码
1 /**********************************************************************
2 * 函数名称: PrintListMem
3 * 功能描述: 打印OMCI内存泄露检测结果
4 * 输入参数: VOID *pvNodeData :链表结点数据指针
5 * INT32U dwNodeNum :链表结点数目
6 ***/
7 static VOID PrintListMem(VOID pvNodeData, INT32U dwNodeNum)
8 {
9 OMCI_MEM_INFO ptMemInfo = (OMCI_MEM_INFO )pvNodeData;
10 printf("[%s(%d)<%s>]%uBytes(Address:%p) were allocated, but unfreed!\n",
11 ptMemInfo->pFileName, ptMemInfo->dwCodeLine, ptMemInfo->pFuncName,
12 ptMemInfo->dwMemSize, (CHAR)ptMemInfo->pvMemAddr);
13 }
14
15 /
16 * 函数名称: OmciCheckMemLeak
17 * 功能描述: OMCI内存泄露检测
18 * 输入参数: T_OMCI_LIST *gpMemLeakCheckList :泄露结点链表指针
19 **/
20 static VOID OmciCheckMemLeak(T_OMCI_LIST gpMemLeakCheckList)
21 {
22 INT32U dwMemLeakNum = OmciGetListNodeNum(gpMemLeakCheckList);
23 if(0 == dwMemLeakNum)
24 {
25 printf(“No Memory Leakage Detected!\n”);
26 return;
27 }
28 printf(“Suspected Memory Leakage Occurrence(%u Time(s)):\n”, dwMemLeakNum);
29 OmciPrintListNode(gpMemLeakCheckList, PrintListMem);
30 }
31
32 /
33 * 函数名称: CompareNodeMem
34 * 功能描述: 可疑泄露结点信息比较
35 * 输入参数: VOID *pNodeData :链表结点数据指针
36 * VOID *pData :待比较外部数据指针
37 * INT32U dwNodeDataSize :链表结点数据大小
38 * 输出参数: NA
39 * 返 回 值: 0:Equal; !0:Unequal
40 * 注意事项: 比较长度为结点数据字节数,即默认与外部数据大小一致
41 ***********************************************************************/
42 static INT8U CompareNodeMem(VOID *pNodeData, VOID *pData, INT32U dwNodeDataSize)
43 {
44 return (((OMCI_MEM_INFO *)pNodeData)->pvMemAddr != ((OMCI_MEM_INFO *)pData)->pvMemAddr);
45 }
复制代码
CompareNodeMem函数中,内存指针匹配时内存大小不一定匹配,此时仍可正常释放,但头守护字节的内存大小字段被越界改写。若要检测这种错误(意义不大),可将函数改写为:
复制代码
1 INT8U CompareNodeMem(VOID *pNodeData, VOID *pData, INT32U dwNodeDataSize)
2 {
3 OMCI_MEM_INFO *ptMemInfo1 = (OMCI_MEM_INFO *)pNodeData;
4 OMCI_MEM_INFO *ptMemInfo2 = (OMCI_MEM_INFO *)pData;
5
6 if(ptMemInfo1->pvMemBufAddr != ptMemInfo2->pvMemBufAddr)
7 return 1;
8
9 if(ptMemInfo1->dwMemSize != ptMemInfo2->dwMemSize)
10 { //因内存指针匹配,故仍可正常释放,但提示某处可能存在内存越界
11 printf(“MemSize Field of Head Guard was Overwritten([%s(%u)<%s>]Addr:%p, Size:%u->%u)!\n”,
12 ptMemInfo2->pFileName, ptMemInfo2->dwCodeLine, ptMemInfo2->pFuncName,
13 ptMemInfo2->pvMemBufAddr, ptMemInfo1->dwMemSize, ptMemInfo2->dwMemSize);
14 }
15
16 return 0;
17 }
复制代码
基于上述私有函数,可进一步构建内存管理和检测的基本接口。
2.3.2 公共接口
若要启用内存泄露检测机制,则分配内存前必须先初始化全局链表gtMemLeakCheckList:
复制代码
1 /**********************************************************************
2 * 函数名称: OmciInitMemCheck
3 * 功能描述: 初始化内存泄露检测机制
4 * 注意事项: 检测内存泄露时必须先调用本函数进行初始化,然后分配内存
5 ***********************************************************************/
6 VOID OmciInitMemCheck(VOID)
7 {
8 OMCI_INIT_MEMINFO();
9 }
复制代码
完成链表初始化工作后,内存分配接口内就可向其插入内存信息:
复制代码
1 /**********************************************************************
2 * 函数名称: OmciAlloc
3 * 功能描述: 动态分配内存并初始化
4 * 输入参数: INT32U dwMemSize :内存管理函数申请到的内存字节数
5 * const CHAR *pFileName :内存管理函数调用处所在文件名
6 * const CHAR *pFuncName :内存管理函数调用处所在函数名
7 * INT32U dwCodeLine :内存管理函数调用处所在代码行号
8 * 输出参数: NA
9 * 返 回 值: VOID *:内存管理函数返回的内存指针地址
10 * 注意事项: 必须通过OMCI_ALLOC宏间接调用,且不可与free配对使用
11 ***********************************************************************/
12 VOID *OmciAlloc(INT32U dwMemSize, const CHAR *pFileName, const CHAR *pFuncName, INT32U dwCodeLine)
13 {
14 if(0 == dwMemSize)
15 {
16 printf("[%s(%u)<%s>] Memmory to be allocated must be larger than Zero Byte!\n",
17 pFileName, dwCodeLine, pFuncName);
18 return NULL;
19 }
20
21 VOID *pvMemBuf = calloc((dwMemSize+OMCI_MEM_HEAD_SIZE+OMCI_MEM_TAIL_SIZE), 1);
22 if(NULL == pvMemBuf)
23 {
24 printf("[%s(%d)<%s>] Allocating %uBytes failed, no memory available!\n",
25 pFileName, dwCodeLine, pFuncName, dwMemSize);
26 return NULL;
27 }
28
29 gtMemStatis.dwAllocBytes += dwMemSize;
30 gtMemStatis.dwAllocTimes++;
31
32 pvMemBuf = GenerateMemChecker(pvMemBuf, dwMemSize);
33 OMCI_INSERT_MEMINFO(pFileName, pFuncName, dwCodeLine, dwMemSize, pvMemBuf);
34
35 return pvMemBuf;
36 }
复制代码
通过头尾守护字节和内存分配信息,释放时可检测内存越界并删除链表结点:
复制代码
1 /**********************************************************************
2 * 函数名称: OmciFree
3 * 功能描述: 释放通过OMCI_ALLOC分配的动态内存
4 * 输入参数: VOID* pvMemBuf :指向待释放内存的指针
5 * const CHAR *pFileName :内存管理函数调用处所在文件名
6 * const CHAR *pFuncName :内存管理函数调用处所在函数名
7 * INT32U dwCodeLine :内存管理函数调用处所在代码行号
8 * 输出参数: NA
9 * 返 回 值: FUNC_STATUS
10 * 注意事项: 必须通过OMCI_FREE宏间接调用,且不可与malloc/calloc等配对使用
11 ********************************************************************/
12 FUNC_STATUS OmciFree(VOID pvMemBuf, const CHAR pFileName, const CHAR pFuncName, INT32U dwCodeLine)
13 {
14 if(NULL == pvMemBuf)
15 {
16 printf("[%s(%d)<%s>] Cannot free Null Address!\n", pFileName, dwCodeLine, pFuncName);
17 return S_NULL_POINTER;
18 }
19
20 INT32U dwMemSize = 0;
21 CHAR *pMemHeader = DeriveMemHeader(pvMemBuf, &dwMemSize);
22
23 //对于Double Free,无论头守护字节是否已被改写,free均会发生段错误;
24 //对于非OMCI_ALLOC申请的内存,free可能造成越界释放。
25 //但本函数难以区分两种情况。综合考虑,决定拦截并警告。
26 if(0 == dwMemSize)
27 {
28 printf(“Warning: [%s(%d)<%s>]Free Memory(%p) that was 1) not allocated by OMCI_ALLOC or 2) freed by OMCI_FREE;\n”,
29 pFileName, dwCodeLine, pFuncName, pvMemBuf);
30 printf(" Both cases are critical, and NO free action takes place to minimize the casualties!!!\n");
31 return S_ILLEGAL_PARAM;
32 }
33
34 if(IsMemHeadOverrun(pMemHeader))
35 {
36 printf(“Warning: [%s(%d)<%s>]Overrun Occurs at Lower Address(%p);\n”,
37 pFileName, dwCodeLine, pFuncName, pvMemBuf);
38 }
39 if(IsMemTailOverrun(pvMemBuf, dwMemSize))
40 {
41 printf(“Warning: [%s(%d)<%s>]Overrun Occurs at Higher Address(%p);\n”,
42 pFileName, dwCodeLine, pFuncName, pvMemBuf);
43 }
44
45 OMCI_REMOVE_MEMINFO(pFileName, pFuncName, dwCodeLine, dwMemSize, pvMemBuf);
46
47 free(pMemHeader);
48
49 gtMemStatis.dwFreeBytes += dwMemSize;
50 gtMemStatis.dwFreeTimes++;
51
52 return S_OK;
53 }
复制代码
程序执行过程中,可随时调用OmciShowMemInfo查看内存分配的统计信息及泄露情况:
复制代码
1 /**********************************************************************
2 * 函数名称: OmciShowMemInfo
3 * 功能描述: 查看OMCI模块当前内存申请与释放信息
4 * 输入参数: NA
5 * 输出参数: NA
6 * 返 回 值: VOID
7 * 注意事项: 当__MEM_LEAK_CHECK已定义时,本函数同时显示可能存在的内存泄露
8 ***********************************************************************/
9 VOID OmciShowMemInfo(VOID)
10 {
11 printf("-------------------Omci Memory Info-------------------\n");
12 printf("%-15s%-15s%-15s%-15s%-15s\n", “AllocBytes”, “AllocTimes”, “FreeBytes”, “FreeTimes”, “”);
13 printf("%-15u%-15u%-15u%-15u%-15u\n", gtMemStatis.dwAllocBytes, gtMemStatis.dwAllocTimes,
14 gtMemStatis.dwFreeBytes, gtMemStatis.dwFreeTimes, gtMemStatis.dwAllocBytes-gtMemStatis.dwFreeBytes);
15
16 OMCI_REPORT_MEMCHECK();
17 }
复制代码
三 测试
本节将对上文实现的堆内存检测机制进行测试。
复制代码
1 CHAR *gpGlobalMem = NULL;
2 VOID GlobalAllocTest(VOID){
3 gpGlobalMem = OMCI_ALLOC(50);
4 }
5 VOID GlobalFreeTest(VOID){
6 OMCI_FREE(gpGlobalMem);
7 }
8
9 INT32S main(VOID)
10 {
11 INT8U ucTestIndex = 1;
12 OMCI_INIT_MEMINFO();
13
14 printf("\n<Test Case %u>: Simple Alloc-Free within Function!\n", ucTestIndex++);
15 CHAR *ptr = OMCI_ALLOC(10);
16 CHAR *ptr1 = OMCI_ALLOC(20);
17 CHAR *ptr2 = OMCI_ALLOC(30);
18 CHAR *ptr3 = OMCI_ALLOC(40);
19 strcpy(ptr, “123456789”);
20 printf(“ptr=%s(PreFree)\n”, ptr);
21 OMCI_FREE(ptr); //为测试需要,此处释放内存后不置ptr为NULL
22 printf(“ptr=%s(PostFree)\n”, ptr);
23 OmciShowMemInfo();
24
25 printf("\n<Test Case %u>: Simple Alloc-Free outside Function!\n", ucTestIndex++);
26 GlobalAllocTest();
27 OmciShowMemInfo();
28
29 printf("\nTest Case %u: Free all Allocated Memories!\n", ucTestIndex++);
30 OMCI_FREE(ptr1);
31 OMCI_FREE(ptr2);
32 OMCI_FREE(ptr3);
33 GlobalFreeTest();
34 OmciShowMemInfo();
35
36 printf("\nTest Case %u: Overrun Downward!\n", ucTestIndex++);
37 CHAR *ptr5 = OMCI_ALLOC(33);
38 *(ptr5-8) = 10;
39 OMCI_FREE(ptr5);
40
41 printf("\nTest Case %u: Overrun Upward!\n", ucTestIndex++);
42 CHAR *ptr6 = OMCI_ALLOC(5);
43 strcpy(ptr6, “123456789”);
44 OMCI_FREE(ptr6);
45
46 printf("\nTest Case %u: Doubly Free!\n", ucTestIndex++);
47 OMCI_FREE(ptr6);
48
49 printf("\nTest Case %u: Free Memory not allocated by OMCI_ALLOC!\n", ucTestIndex++);
50 CHAR *ptr7 = malloc(44);
51 OMCI_FREE(ptr7);
52
53 return 0;
54 }
复制代码
关闭-D__MEM_LEAK_CHECK编译选项时,测试结果如下所示(不检查堆内存泄露):
复制代码
1 <Test Case 1>: Simple Alloc-Free within Function!
2 ptr=123456789(PreFree)
3 ptr=123456789(PostFree)
4 -------------------Omci Memory Info-------------------
5 AllocBytes AllocTimes FreeBytes FreeTimes
6 100 4 10 1 90
7
8 <Test Case 2>: Simple Alloc-Free outside Function!
9 -------------------Omci Memory Info-------------------
10 AllocBytes AllocTimes FreeBytes FreeTimes
11 150 5 10 1 140
12
13 Test Case 3: Free all Allocated Memories!
14 -------------------Omci Memory Info-------------------
15 AllocBytes AllocTimes FreeBytes FreeTimes
16 150 5 150 5 0
17
18 Test Case 4: Overrun Downward!
19 Warning: [Omci_Main.c(61)]Overrun Occurs at Lower Address(0x8e38050);
20
21 Test Case 5: Overrun Upward!
22 Warning: [Omci_Main.c(67)]Overrun Occurs at Higher Address(0x8e38010);
23
24 Test Case 6: Doubly Free!
25 Warning: [Omci_Main.c(70)]Free Memory(0x8e38010) that was 1) not allocated by OMCI_ALLOC or 2) freed by OMCI_FREE;
26 Both cases are critical, and NO free action takes place to minimize the casualties!!!
27
28 Test Case 7: Free Memory Not Allocated by OMCI_ALLOC!
29 Warning: [Omci_Main.c(74)]Free Memory(0x8e38048) that was 1) not allocated by OMCI_ALLOC or 2) freed by OMCI_FREE;
30 Both cases are critical, and NO free action takes place to minimize the casualties!!!
复制代码
开启-D__MEM_LEAK_CHECK编译选项时,测试结果如下所示:
复制代码
1 <Test Case 1>: Simple Alloc-Free within Function!
2 ptr=123456789(PreFree)
3 ptr=123456789(PostFree)
4 -------------------Omci Memory Info-------------------
5 AllocBytes AllocTimes FreeBytes FreeTimes
6 100 4 10 1 90
7 Suspected Memory Leakage Occurrence(3 Time(s)):
8 [Omci_Main.c(37)]20Bytes(Address:0x8428078) were allocated, but unfreed!
9 [Omci_Main.c(38)]30Bytes(Address:0x84280c8) were allocated, but unfreed!
10 [Omci_Main.c(39)]40Bytes(Address:0x8428120) were allocated, but unfreed!
11
12
13 <Test Case 2>: Simple Alloc-Free outside Function!
14 -------------------Omci Memory Info-------------------
15 AllocBytes AllocTimes FreeBytes FreeTimes
16 150 5 10 1 140
17 Suspected Memory Leakage Occurrence(4 Time(s)):
18 [Omci_Main.c(37)]20Bytes(Address:0x8428078) were allocated, but unfreed!
19 [Omci_Main.c(38)]30Bytes(Address:0x84280c8) were allocated, but unfreed!
20 [Omci_Main.c(39)]40Bytes(Address:0x8428120) were allocated, but unfreed!
21 [Omci_Main.c(17)]50Bytes(Address:0x8428180) were allocated, but unfreed!
22
23
24 Test Case 3: Free all Allocated Memories!
25 -------------------Omci Memory Info-------------------
26 AllocBytes AllocTimes FreeBytes FreeTimes
27 150 5 150 5 0
28 No Memory Leakage Detected!
29
30 Test Case 4: Overrun Downward!
31 Warning: [Omci_Main.c(61)]Overrun Occurs at Lower Address(0x84280c8);
32
33 Test Case 5: Overrun Upward!
34 Warning: [Omci_Main.c(67)]Overrun Occurs at Higher Address(0x8428038);
35
36 Test Case 6: Doubly Free!
37 Warning: [Omci_Main.c(70)]Free Memory(0x8428038) that was 1) not allocated by OMCI_ALLOC or 2) freed by OMCI_FREE;
38 Both cases are critical, and NO free action takes place to minimize the casualties!!!
39
40 Test Case 7: Free Memory Not Allocated by OMCI_ALLOC!
41 Warning: [Omci_Main.c(74)]Free Memory(0x84280c0) that was 1) not allocated by OMCI_ALLOC or 2) freed by OMCI_FREE;
42 Both cases are critical, and NO free action takes place to minimize the casualties!!!
复制代码
四 后记
在大型项目中,系统支撑层面通常会封装统一的内存申请/释放接口,以便管理和优化。发生内存泄露时,外部检测工具所报告的泄露代码位于接口内,对排障帮助有限(除非提供堆栈回溯)。而本文提供的检测机制天然地寄生于管理接口之上,能准确地指示泄露内存的分配现场。
若要直接接管malloc/free等库函数调用,可采用如下方式:
复制代码
1 //Omci_Alloc.h
2 #ifndef OMCI_INNER_ALLOC
3 #define malloc(dwMemSize) OmciAlloc((dwMemSize), FILE, FUNC_NAME, LINE)
4 #define free(pvMemBuf) OmciFree((pvMemBuf), FILE, FUNC_NAME, LINE)
5 #endif
6
7 //Omci_Alloc.c
8 #define OMCI_INNER_ALLOC
9 #include “Omci_Alloc.h”
复制代码
如此可避免OmciAlloc/ OmciFree内递归调用库函数。若在Omci_Alloc.c内实现内存泄露检测的链表操作(单链表即可),则不必“污染”双向循环链表函数集。
此外,为使检测代码的简单易懂,文中未对其进行优化和异常保护。
分类: 编程实践
好文要顶 关注我 收藏该文
clover_toeic
粉丝 - 429 关注 - 15
+加关注
90
« 上一篇: C语言内存使用的常见问题及解决之道
» 下一篇: 我的代码重构经验
posted @ 2014-07-03 11:39 clover_toeic 阅读(1876) 评论(5) 编辑 收藏 举报
刷新评论刷新页面返回顶部
登录后才能查看或发表评论,立即 登录 或者 逛逛 博客园首页
【推荐】阿里云2核2G云服务器低至99元/年,百款云产品优惠享不停
编辑推荐:
· Three.js 进阶之旅:物理效果-3D乒乓球小游戏
· ASP.NET Core 使用 filter 和 redis 实现接口防重
· 聊聊「订单」业务的设计与实现
· 记一次生产频繁发生 FullGC 问题
· .NET Task 揭秘:async 与 AsyncMethodBuilder
即构专区:
· 即构✖叮咚课堂:行业第一套AI课堂解决方案是怎么被实现的?
· 即构推出低延迟直播产品L3,可将直播延迟降到1s
· 在线自习室场景爆发,在线教育平台用户时间争夺战打响
· 教师节专题:AI互动课来了,即构方案助推在线教育创新升级
· 元宇宙场景技术实践|实现“虚拟人”自由
昵称: clover_toeic
园龄: 9年6个月
粉丝: 429
关注: 15
+加关注
< 2023年3月 >
日 一 二 三 四 五 六
26 27 28 1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31 1
2 3 4 5 6 7 8
随笔分类
编程实践(52)
工具介绍(5)
设计优化(5)
思维火花(4)
算法实现(2)
通信系统(3)
硬件电路(4)
语言知识(19)
阅读排行榜
- C语言字节对齐问题详解(109418)
- C语言函数调用栈(一)(65861)
- Python标准输出重定向(54612)
- C语言预处理命令详解(44701)
- 缓冲区溢出详解(44577)
评论排行榜 - C语言字节对齐问题详解(16)
- 基于VLAN的二三层转发(15)
- C语言表驱动法编程实践(14)
- C语言函数调用栈(二)(13)
- 我的代码重构经验(12)
推荐排行榜 - C语言函数调用栈(一)(112)
- C语言字节对齐问题详解(88)
- Linux虚拟地址空间布局(48)
- C语言预处理命令详解(41)
- 基于VLAN的二三层转发(33)
最新评论 - Re:C语言头文件组织与包含原则
感谢大佬教导
–早睡早起吖
2. Re:C语言表驱动法编程实践
博主从点滴代码上升至理论,用理论认阐述本质,唯有如此才能走的更远,佩服。
–熊已出没
3. Re:Java编码常见的Log日志打印问题
@clover_toeic 打印日志而已,加不加空格的影响是啥呢,有啥特别的含义吗…
–winner_0715
4. Re:C语言函数调用栈(三)
还有一个问题,如果没有开启编译器的优化,完全按照最原始的函数返回方式处理,代码仍然按照6.4开头的代码,情况是不是:main函数调用func前先在栈中预留一个结构体变量的空间,然后调用func,fun…
–DarthVagen
5. Re:C语言函数调用栈(三)
感觉有个小错误,图16第二个红框处,0x.72和0x.75两条指令,是将%ebp - 12地址处的值赋给&edx地址处(亦即locStrtMain.member1),但是根据图15,%ebp - 12…
–DarthVagen
Copyright © 2023 clover_toeic
Powered by .NET 7.0 on Kubernetes