記憶體篇之堆洩漏
“堆洩漏”即常說的記憶體洩漏,是嵌入式軟體裡的常見問題,會導致軟體執行一段時間後記憶體耗盡。
什麼是”堆洩漏”?
記憶體分配和釋放的操作是程式設計師根據需要動態隨機發起,程式本身(或編譯工具)無法自動判斷某塊已分配的記憶體什麼時候不再被使用,必須由程式設計師自己手動呼叫free釋放,以便為其他程式騰出空間。而一旦程式設計師忘記釋放某塊記憶體,它就不能回到可用記憶體,系統總的可分配記憶體就隨之減少,這就是記憶體洩漏。注意這裡的記憶體特指堆(heap),只有堆記憶體才需要程式設計師自己控制分配和釋放。所以記憶體洩漏和堆洩漏是同一概念。
新手對洩漏這個詞往往感到不理解,不就是分配後忘記釋放,怎麼叫洩漏呢?叫記憶體丟失不是更通俗麼?
關於這點,可以打個比方,分配記憶體就是從銀行貸款,而釋放記憶體就是給銀行還錢。如果有人借了錢卻賴帳不還,那麼銀行可支配的錢就會減少,銀行總資產就被損失或洩漏。類似,堆是一塊固定大小記憶體,“借”給不同程式使用,如果某個程式只借不還,堆管理所能支配的記憶體就減少,因此記憶體洩漏是針對系統中總的可支配記憶體資源來說,而並不是實體記憶體真的丟失。從這個角度理解,leak絕對比lost更準確生動:一種資源在封閉系統中迴圈使用,如果部分資源無法回到迴圈,不正是洩漏到封閉系統之外了麼?
借錢不還的銀行客戶越來越多,最終銀行就會因為沒錢放貸週轉而破產。同樣發生記憶體洩漏,直接的表現就是軟體執行越來越慢,最終甚至因分不到記憶體而崩潰。(所以說一定要判斷malloc
洩漏原因及對策
所有老師都會強調malloc後一定要有free,但實際編寫複雜程式碼時,記憶體洩漏幾乎不可避免。比如下面多分支退出,某分支忘記釋放已分配的記憶體,就導致洩漏:
void MyFunc(int size)
{
char* p= malloc(size);
if( !GetStringFrom( p, nSize ) )
{
printf(“Error”);
return;
}
…//using the string pointed by p;
free(p);
}
無法完全避免記憶體洩漏,只能通過一些程式設計原則減少洩漏的概率:
1) 減少多分支退出而遺漏free,可用goto語句保證函式只有一個退出點。
2) 保證在同一層上使用malloc/free對,也就是說不要在子函式中malloc,在外層主函式free。這種記憶體在不同層次分配釋放會使邏輯層次混亂,很容易導致記憶體洩漏。
char* AllocStrFromHeap(int len)
{
char *pstr;
if ( len <= 0 ) return NULL;
return ( char* ) malloc( len );
}
相反如果在主函式中malloc並使用記憶體,而在某子函式中釋放參數所指記憶體,可能導致主函式中出現野指標(後續)。
3)人工review程式碼查詢記憶體洩漏很困難,可藉助工具快速檢測,如boundchecker/pc-lint等都能通過自動掃描程式碼找到記憶體洩漏。
隱式洩漏
是指某記憶體已使用完,明明可以早點free掉,卻非等到軟體退出前才釋放,俗稱“佔著XX不XX”,雖然程式最終釋放了所有記憶體,嚴格意義上沒有洩漏,但某些場合隱式洩露同樣會導致嚴重後果:比如某長期執行的伺服器程式,如果不斷分配而不及時釋放記憶體,最後系統很可能在執行中途就因堆記憶體耗盡而crash,因此記憶體使用過程中,不但要確保釋放記憶體,而且用完要儘快釋放,而不要全等到退出前釋放,以消除隱式洩漏,確保記憶體佔用峰值不超過系統堆資源上限。