本文基於鏈表實現C語言堆內存的檢測機制,可檢測內存泄露、越界和重複釋放等操做問題。html
本文僅提供即視代碼層面的檢測機制,不考慮編譯連接級的注入或鉤子。此外,該機制暫未考慮併發保護。數據結構
相關性文章參見:多線程
《C語言通用雙向循環鏈表操做函數集》併發
《C語言內存使用的常見問題及解決之道》ide
堆內存泄漏檢測機制的基本原理是截獲對內存分配和釋放函數的調用,從而跟蹤每塊內存的生命週期。例如,每次成功分配一塊內存後,將內存分配信息(如指向它的指針、文件名、函數名、行號和申請字節數等)加入一個全局鏈表中;每當釋放一塊內存時,再從鏈表中刪除相應的內存信息。這樣,當程序結束時,鏈表中剩餘的內存信息結點就對應那些未被釋放的內存。函數
當運行環境支持斷點調試時,記錄下內存分配的序號會頗有用;當運行環境支持堆棧回溯時,可記錄回溯信息以便觀察泄露時的函數調用棧。此外,可根據使用者指定的文件名和行號信息過濾鏈表結點,以輸出指定內存申請代碼處的回溯信息。工具
對於隱式內存泄露,可在程序運行過程當中監控當前內存的總使用量和分配釋放狀況。以分配內存時的文件名和行號爲索引,遍歷鏈表結點便可計算出各處已分配但未釋放的內存總量。若在連續多個時間間隔內,某文件中某行所分配的內存總量不斷增加,則基本可肯定屬於隱式內存泄露(尤爲是多線程引發的)。性能
當模塊提供動態內存管理的封裝接口時,可採用「紅區」技術檢測內存越界。例如,接口內每次申請比調用者所需更大的內存,將其首尾若干字節(守護字節,Guard Bytes)設置爲特殊值,僅將中間部分的內存返回給調用者使用。經過檢查特殊字節是否被改寫,便可獲知是否發生內存越界。其結構示意圖以下:測試
須要注意的是,這些檢測機制均會形成目標程序性能上的損失,所以最好能在編譯時(經過編譯選項)或運行時(經過設置使能標誌等)可選地激活。優化
本節將基於《C語言通用雙向循環鏈表操做函數集》一文中的鏈表接口,實現堆內存檢測機制。
文中「OMCI_」和「Omci」前綴爲代碼所在模塊名信息,使用接口時可按需修改這些前綴。
定義內存管理信息結構體以下:
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};
爲完成基本的內存分配和釋放工做,必須調用系統提供的相應庫函數(如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個守護字節,其中Byte0~3標記OMCI內存管理特徵碼0xA5(二進制10100101),前兩字節用於越界檢測,後兩字節用於調用匹配。Byte4~7記錄申請的內存長度供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)用於控制內存檢測功能的開啓和關閉。
經過鏈表儲存內存分配信息時,鏈表自身也須要分配和釋放內存。若不與外界的普通內存分配和釋放區分,將會出現無限循環遞歸調用。所以,首先對鏈表函數集稍加改造:
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 }
基於上述私有函數,可進一步構建內存管理和檢測的基本接口。
若要啓用內存泄露檢測機制,則分配內存前必須先初始化全局鏈表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", "<UnFreeBytes>"); 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 <UnFreeBytes> 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 <UnFreeBytes> 11 150 5 10 1 140 12 13 Test Case 3: Free all Allocated Memories! 14 -------------------Omci Memory Info------------------- 15 AllocBytes AllocTimes FreeBytes FreeTimes <UnFreeBytes> 16 150 5 150 5 0 17 18 Test Case 4: Overrun Downward! 19 Warning: [Omci_Main.c(61)<main>]Overrun Occurs at Lower Address(0x8e38050); 20 21 Test Case 5: Overrun Upward! 22 Warning: [Omci_Main.c(67)<main>]Overrun Occurs at Higher Address(0x8e38010); 23 24 Test Case 6: Doubly Free! 25 Warning: [Omci_Main.c(70)<main>]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)<main>]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 <UnFreeBytes> 6 100 4 10 1 90 7 Suspected Memory Leakage Occurrence(3 Time(s)): 8 [Omci_Main.c(37)<main>]20Bytes(Address:0x8428078) were allocated, but unfreed! 9 [Omci_Main.c(38)<main>]30Bytes(Address:0x84280c8) were allocated, but unfreed! 10 [Omci_Main.c(39)<main>]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 <UnFreeBytes> 16 150 5 10 1 140 17 Suspected Memory Leakage Occurrence(4 Time(s)): 18 [Omci_Main.c(37)<main>]20Bytes(Address:0x8428078) were allocated, but unfreed! 19 [Omci_Main.c(38)<main>]30Bytes(Address:0x84280c8) were allocated, but unfreed! 20 [Omci_Main.c(39)<main>]40Bytes(Address:0x8428120) were allocated, but unfreed! 21 [Omci_Main.c(17)<GlobalAllocTest>]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 <UnFreeBytes> 27 150 5 150 5 0 28 No Memory Leakage Detected! 29 30 Test Case 4: Overrun Downward! 31 Warning: [Omci_Main.c(61)<main>]Overrun Occurs at Lower Address(0x84280c8); 32 33 Test Case 5: Overrun Upward! 34 Warning: [Omci_Main.c(67)<main>]Overrun Occurs at Higher Address(0x8428038); 35 36 Test Case 6: Doubly Free! 37 Warning: [Omci_Main.c(70)<main>]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)<main>]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內實現內存泄露檢測的鏈表操做(單鏈表便可),則沒必要「污染」雙向循環鏈表函數集。
此外,爲使檢測代碼的簡單易懂,文中未對其進行優化和異常保護。