1. 程式人生 > >記憶體篇之堆洩漏

記憶體篇之堆洩漏

    “堆洩漏即常說的記憶體洩漏,是嵌入式軟體裡的常見問題,會導致軟體執行一段時間後記憶體耗盡。

什麼是堆洩漏

    記憶體分配和釋放的操作是程式設計師根據需要動態隨機發起,程式本身(或編譯工具)無法自動判斷某塊已分配的記憶體什麼時候不再被使用,必須由程式設計師自己手動呼叫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掉,卻非等到軟體退出前才釋放,俗稱“佔著XXXX”,雖然程式最終釋放了所有記憶體,嚴格意義上沒有洩漏,但某些場合隱式洩露同樣會導致嚴重後果:比如某長期執行的伺服器程式,如果不斷分配而不及時釋放記憶體,最後系統很可能在執行中途就因堆記憶體耗盡而crash,因此記憶體使用過程中,不但要確保釋放記憶體,而且用完要儘快釋放,而不要全等到退出前釋放,以消除隱式洩漏,確保記憶體佔用峰值不超過系統堆資源上限。