摘要:經過介紹內存泄漏問題原理及檢視方法,但願後續可以從編碼檢視環節就杜絕內存泄漏致使的網上問題發生。
最近部門不一樣產品接連出現內存泄漏致使的網上問題,具體表現爲單板在現網運行數月之後,由於內存耗盡而致使單板復位現象。一方面,內存泄漏問題屬於低級錯誤,此類問題遺漏到現網,影響很壞;另外一方面,因爲內存泄漏問題極可能致使單板運行固定時間之後就復位,只能經過批量升級才能解決,實際影響也很惡劣。同時,接連出現此類問題,尤爲是其中一例問題仍是咱們老員工修改引入,說明咱們很多員工對內存泄漏問題認識仍是不夠深入的。本文經過介紹內存泄漏問題原理及檢視方法,但願後續可以從編碼檢視環節就杜絕此類問題發生。segmentfault
說明:預防內存泄漏問題有多種方法,如增強代碼檢視、工具檢測和內存測試等,本文彙集於開發人員能力提高方面。app
內存泄漏問題只有在使用堆內存的時候纔會出現,棧內存不存在內存泄漏問題,由於棧內存會自動分配和釋放。C代碼中堆內存的申請函數是malloc,常見的內存申請代碼以下:函數
char *info = NULL; /**轉換後的字符串**/ info = (char*)malloc(NB_MEM_SPD_INFO_MAX_SIZE); if( NULL == info) { (void)tdm_error("malloc error!\n"); return NB_SA_ERR_HPI_OUT_OF_MEMORY; }
因爲malloc函數返回的其實是一個內存地址,因此保存堆內存的變量必定是一個指針(除非代碼編寫極其不規範)。再重複一遍,保存堆內存的變量必定是一個指針,這對本文主旨的理解很重要。固然,這個指針能夠是單指針,也能夠是多重指針。工具
malloc函數有不少變種或封裝,如g_malloc、g_malloc0、VOS_Malloc等,這些函數最終都會調用malloc函數。測試
看到本小節標題,可能有些同窗有疑惑,上一小節中的malloc函數,不就是堆內存的獲取方法嗎?的確是,經過malloc函數申請是最直接的獲取方法,若是隻知道這種堆內存獲取方法,就容易掉到坑裏了。通常的來說,堆內存有以下兩種獲取方法:編碼
方法一:將函數返回值直接賦給指針,通常表現形式以下:指針
char *local_pointer_xx = NULL; local_pointer_xx = (char*)function_xx(para_xx, …);
該類涉及到內存申請的函數,返回值通常都指針類型,例如:code
GSList* g_slist_append (GSList *list, gpointer data)
方法二:將指針地址做爲函數返回參數,經過返回參數保存堆內存地址,通常表現形式以下:blog
int ret; char *local_pointer_xx = NULL; /**轉換後的字符串**/ ret = (char*)function_xx(..., &local_pointer_xx, ...);
該類涉及到內存申請的函數,通常都有一個入參是雙重指針,例如:接口
__STDIO_INLINE _IO_ssize_t getline (char **__lineptr, size_t *__n, FILE *__stream)
前面說經過malloc申請內存,就屬於方法一的一個具體表現形式。其實這兩類方法的本質是同樣的,都是函數內部間接申請了內存,只是傳遞內存的方法不同,方法一經過返回值傳遞內存指針,方法二經過參數傳遞內存指針。
最多見的內存泄漏問題,包含如下三個要素:
要素一:函數內有局部指針變量定義;
要素二:對該局部指針有經過上一小節中「兩種堆內存獲取方法」之一獲取內存;
要素三:在函數返回前(含正常分支和異常分支)未釋放該內存,也未保存到其它全局變量或返回給上一級函數。
稍微使用過C語言編寫代碼的人,都應該知道堆內存申請以後是須要釋放的。但爲什麼還這麼容易出現內存泄漏問題呢?一方面,是開發人員經驗不足、意識不到位或一時疏忽致使;另外一方面,是內存釋放誤區致使。不少開發人員,認爲要釋放的內存應該侷限於如下兩種:
1)直接使用內存申請函數申請出來的內存,如malloc、g_malloc等;
2)該開發人員熟悉的接口中,存在內存申請的狀況,如iBMC的兄弟,都應該知道調用以下接口須要釋放list指向的內存:
dfl_get_object_list(const char* class_name, GSList **list)
按照以上思惟編寫代碼,一旦遇到不熟悉的接口中須要釋放內存的問題,就徹底沒有釋放內存的意識,內存泄漏問題就天然產生了。
檢視內存泄漏問題,關鍵仍是要養成良好的編碼檢視習慣。與內存泄漏三要素對應,需
要作到以下三點:
(1)在函數中看到有局部指針,就要警戒內存泄漏問題,養成進一步排查的習慣
(2)分析對局部指針的賦值操做,是否屬於前面所說的「兩種堆內存獲取方法」之一,若是是,就要分析函數返回的指針到底指向啥?是全局數據、靜態數據仍是堆內存?對於不熟悉的接口,要找到對應的接口文檔或源代碼分析;又或者看看代碼中其它地方對該接口的引用,是否進行了內存釋放;
(3)若是確認對局部指針存在內存申請操做,就須要分析該內存的去向,是會被保存在全局變量嗎?又或者會被做爲函數返回值嗎?若是都不是,就須要排查函數全部有」return「的地方,保證內存被正確釋放。