作為一名專業(yè)的嵌入式菜鳥,在編寫嵌入式軟件的時候,無可避免地出現(xiàn)內(nèi)存泄漏的問題,比如,數(shù)組越界,空指針,野指針,等等。 在遇到內(nèi)存泄漏的時候,作為菜鳥應(yīng)該如何定位排查和分析解決呢? 1.內(nèi)存泄漏問題原理
1.1-堆內(nèi)存在C代碼中的存儲方式
內(nèi)存泄漏問題只有在使用堆內(nèi)存的時候才會出現(xiàn),棧內(nèi)存不存在內(nèi)存泄漏問題,因為棧內(nèi)存會自動分配和釋放。C代碼中堆內(nèi)存的申請函數(shù)是malloc,常見的內(nèi)存申請代碼如下:
char *info = NULL; /**轉(zhuǎn)換后的字符串**/
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函數(shù)返回的實際上是一個內(nèi)存地址,所以保存堆內(nèi)存的變量一定是一個指針(除非代碼編寫極其不規(guī)范)。
再重復(fù)一遍,保存堆內(nèi)存的變量一定是一個指針,這對本文主旨的理解很重要。當(dāng)然,這個指針可以是單指針,也可以是多重指針。
malloc函數(shù)有很多變種或封裝,如g_malloc、g_malloc0、VOS_Malloc等,這些函數(shù)最終都會調(diào)用malloc函數(shù)。
1.2-堆內(nèi)存的獲取方法
看到本小節(jié)標(biāo)題,可能有些同學(xué)有疑惑,上一小節(jié)中的malloc函數(shù),不就是堆內(nèi)存的獲取方法嗎?
的確是,通過malloc函數(shù)申請是最直接的獲取方法,如果只知道這種堆內(nèi)存獲取方法,就容易掉到坑里了。一般的來講,堆內(nèi)存有如下兩種獲取方法:
方法一:將函數(shù)返回值直接賦給指針,一般表現(xiàn)形式如下:
char *local_pointer_xx = NULL;
local_pointer_xx = (char*)function_xx(para_xx, …);該類涉及到內(nèi)存申請的函數(shù),返回值一般都指針類型,例如:
GSList* g_slist_append(GSList *list, gpointer data);方法二:將指針地址作為函數(shù)返回參數(shù),通過返回參數(shù)保存堆內(nèi)存地址,一般表現(xiàn)形式如下:
int ret;
char *local_pointer_xx = NULL; /**轉(zhuǎn)換后的字符串**/
ret = (char*)function_xx(..., &local_pointer_xx, ...);該類涉及到內(nèi)存申請的函數(shù),一般都有一個入?yún)⑹请p重指針,例如:
__STDIO_INLINE _IO_ssize_t;
getline (char **__lineptr, size_t *__n, FILE *__stream);前面說通過malloc申請內(nèi)存,就屬于方法一的一個具體表現(xiàn)形式。其實這兩類方法的本質(zhì)是一樣的,都是函數(shù)內(nèi)部間接申請了內(nèi)存,只是傳遞內(nèi)存的方法不一樣,方法一通過返回值傳遞內(nèi)存指針,方法二通過參數(shù)傳遞內(nèi)存指針。
1.3-內(nèi)存泄漏三要素
最常見的內(nèi)存泄漏問題,包含以下三個要素:
要素一:函數(shù)內(nèi)有局部指針變量定義;
要素二:對該局部指針有通過上一小節(jié)中“兩種堆內(nèi)存獲取方法”之一獲取內(nèi)存;
要素三:在函數(shù)返回前(含正常分支和異常分支)未釋放該內(nèi)存,也未保存到其它全局變量或返回給上一級函數(shù)。
1.4-內(nèi)存釋放誤區(qū)
稍微使用過C語言編寫代碼的人,都應(yīng)該知道堆內(nèi)存申請之后是需要釋放的。但為何還這么容易出現(xiàn)內(nèi)存泄漏問題呢?
一方面,是開發(fā)人員經(jīng)驗不足、意識不到位或一時疏忽導(dǎo)致;另一方面,是內(nèi)存釋放誤區(qū)導(dǎo)致。很多開發(fā)人員,認為要釋放的內(nèi)存應(yīng)該局限于以下兩種:
1) 直接使用內(nèi)存申請函數(shù)申請出來的內(nèi)存,如malloc、g_malloc等;
2)該開發(fā)人員熟悉的接口中,存在內(nèi)存申請的情況,如iBMC的兄弟,都應(yīng)該知道調(diào)用如下接口需要釋放list指向的內(nèi)存:
dfl_get_object_list(constchar* class_name, GSList **list);按照以上思維編寫代碼,一旦遇到不熟悉的接口中需要釋放內(nèi)存的問題,就完全沒有釋放內(nèi)存的意識,內(nèi)存泄漏問題就自然產(chǎn)生了。
2.內(nèi)存泄漏問題檢視方法
檢視內(nèi)存泄漏問題,關(guān)鍵還是要養(yǎng)成良好的編碼檢視習(xí)慣。與內(nèi)存泄漏三要素對應(yīng),需
要做到如下三點:
1) 在函數(shù)中看到有局部指針,就要警惕內(nèi)存泄漏問題,養(yǎng)成進一步排查的習(xí)慣
2) 分析對局部指針的賦值操作,是否屬于前面所說的“兩種堆內(nèi)存獲取方法”之一,如果是,就要分析函數(shù)返回的指針到底指向啥?
是全局數(shù)據(jù)、靜態(tài)數(shù)據(jù)還是堆內(nèi)存?對于不熟悉的接口,要找到對應(yīng)的接口文檔或源代碼分析;又或者看看代碼中其它地方對該接口的引用,是否進行了內(nèi)存釋放;
3) 如果確認對局部指針存在內(nèi)存申請操作,就需要分析該內(nèi)存的去向,是會被保存在全局變量嗎?又或者會被作為函數(shù)返回值嗎?如果都不是,就需要排查函數(shù)所有有”return“的地方,保證內(nèi)存被正確釋放。
???????????????? END ???????????????
關(guān)注我的微信公眾號,回復(fù)“星球”加入知識星球,有問必答。
點擊“閱讀原文”查看知識星球詳情,歡迎點分享、收藏、點贊、在看。