Global Sources
電子工程專輯
 
電子工程專輯 > 記憶體/儲存
 
 
記憶體/儲存  

動態記憶體分配的優缺點分析

上網時間: 2008年08月05日     打印版  Bookmark and Share  字型大小:  

關鍵字:動態記憶體分配  堆積記憶體  記憶體泄漏 

C和C++中的每個物件都有以下三種儲存期(storage duration)的一種,即靜態、自動和動態:具有靜態儲存期的物件在程式啟動時分配記憶體,且一直保持在固定位置,直到程式結束;具有自動儲存期的物件在進入模組(如典型的函式本體)時分配記憶體,在退出模組時回收記憶體;具有動態儲存期的物件在程式呼叫分配函式(如C中的malloc或C++中的operator new)時分配記憶體,物件的儲存空間一直保持,直到程式將物件的位址傳送給相應的儲存單元分配回收函式(deallocation function),如C中的free和C++中的operator delete。

雖然在不使用靜態和自動分配功能的條件下,幾乎不可能編寫出可用的程式,但沒有動態分配功能的程式是完全可行的。現在讓我們看看支援和不支援動態分配的各種情況。

快速分配

靜態儲存分配通常沒有執行時(run-time)開銷。靜態分配的物件初始化可能在執行中產生,但分配本身通常不佔用時間(當然,分配仍要佔用儲存空間)。然而,使用靜態記憶體收集或大或小物件的各種方法都會浪費記憶體,並對程式行為施以任意的限制。

例如,假設應用程式使用兩種不同容量的儲存區,如緩衝區和儲存區塊。目標系統可能有足夠的記憶體透過靜態方式分配100個緩衝區或250個儲存區塊。那麼你是否應該劃分出給50個緩衝區和125個儲存區塊使用的空間呢?或者一個典型的應用程式更可能需要最多60個緩衝區但只有100個儲存區塊?當程式需要80個緩衝區但儲存區塊只有50個時又怎麼辦呢?

自動記憶體分配的執行時開銷通常非常低,每次分配函式的進入和退出通常只需一條或兩條指令。在堆疊空間非常有限的微控制器上,成本可能會高一些。自動儲存方法使用記憶體的效率非常高,但對於生存期遠大於函式呼叫的物件來說毫無用處。

相較之下,動態記憶體分配的速度要比靜態或自動分配慢得多。在請求分配所需的記憶體空間時,對C或C++中的malloc或operator new呼叫可能要執行數十條甚至有時數百條指令。而透過呼叫C或C++中的free或operator delete進行記憶體回收也需要相當的指令數。

不過我們也可以從動態記憶體分配中獲益:與靜態記憶體分配相較它們能更靈活地使用記憶體。使用動態記憶體可能會使每個物件所需的記憶體增加少許開銷,因此與其使用100個靜態分配的緩衝區,還不如保留只有99或98個動態分配緩衝區的空間。這樣任何程式的執行可以最多有98個緩衝區和零儲存區塊,或最多123個儲存區塊和零緩衝區,或在不超過總可用記憶體條件下緩衝區和儲存區塊容量的各種組合。

風險提示

動態記憶體分配可能帶來很大的風險。一些開發人員,特別是嵌入式系統開發人員會發現這些風險太大以至於不能接受,進而遵循MISRA-C的建議(MISRA是英國電機產業軟體可靠性協會)。這些建議包括:不應該使用動態堆積記憶體(heap memory)分配(這是MISRA-C中規則20.4所要求的)。

這個規則不包括函式calloc、malloc、realloc和free的使用。

動態記憶體分配會出現未規定(unspecified)、未定義(undefined)和執行-定義(implementation-defined)等各種行為,並且還有許多其他潛在的缺陷。動態堆積記憶體分配可能導致記憶體泄漏、數據不一致、記憶體耗盡以及非確定性的行為。

雖然我在用詞上有些不夠清晰,但基本上已涵蓋了大多數主要的問題。這些問題同樣會涉及C++中的operator new和operator delete。以下將詳細介紹這些問題。

未定義行為是指一個程式在執行了一些編譯器和執行時系統不需要檢測(通常是因為它們無法檢測)的錯誤時,所表現出來的行為。例如,向記憶體釋放一個已經被釋放或尚未分配的指針,這時就會產生未定義的行為。未定義行為通常會對堆積記憶體──支援動態記憶體的數據結構造成破壞。堆積記憶體的破壞通常會影響到其他數據和執行結構,最終導致程式崩潰。

執行-定義和未規定行為,是指正確的程式在不同平台上表現出來的行為變化。執行-定義行為與未規定行為之間的差異很簡單,即每個編譯器必須解釋執行-定義行為,但不需要解釋未規定行為。例如,當程式呼叫‘malloc’請求零位元組記憶體分配,那麼這次呼叫的返回值將是執行-定義行為。它可能是一個空指針,也可能真的指向已分配的記憶體。如果指向記憶體,該記憶體的容量是未規定的。

在某種意義上,記憶體泄漏並不是一個真正的問題,記憶體耗盡才是大問題。記憶體中某個地方的泄漏並不會造成任何實質性損害。隨著時間的推移,許多泄漏的累積將變成大問題,它會耗盡可用記憶體,消耗程式資源,使程式無法繼續執行。將記憶體泄漏和記憶體耗盡分成兩種症狀完全是多餘的。

我不能確定MISRA指南中數據不一致性的含義。目前我並未找到找到一致性用途。一些文章將它當作堆積記憶體破壞(heap corruption)的另一個術語,也有些文章用它來描述單一動態分配物件中的值損壞。不管數據不一致性是什麼,它可能只是未定義行為的另外一種表現形式,我不能確定是否應得到特別關注。

malloc和operator new的普通執行具有非確定性行為。也就是說分配請求的執行時間會隨請求的大小和堆積記憶體的當前狀態而變化,有時甚至非常顯著。這種不確定行為會在即時系統中引起嚴重的問題,因為這些即時系統對特定事件的持續時間有固定的上限要求。

如果你還沒見過malloc和free是如何執行的,可以參考Kernighan和Ritchie編寫的《C語言經典介紹》第8章。書中描述了malloc和free的完整執行過程,其中包括程式碼和說明,很值得一讀。

許多即時作業系統(RTOS)提供標準malloc和free的替代函式,它們能從用戶提供的較大記憶體區域(通常稱為記憶體池)中分配和釋放固定大小的儲存區塊。編寫具有確定性短暫執行時間的記憶體池分配函式相當容易。

MISRA指南沒有提及碎片問題,但也許應該提及。在對不同容量的儲存區塊進行許多次分配和回收後,會在堆積記憶體中留下許多零碎的、程式似乎無法使用的區塊,這就是記憶體碎片問題。取決於分配機制,碎片可能會增加分配時間,因而降低執行時性能,也可能會導致記憶體耗盡。使用基於記憶體池的分配機制通常可減少碎片。

儘管存在這些潛在的危險,但許多問題很容易透過動態記憶體分配而不是靜態分配解決,因此你不必拋棄動態分配方法。一些系統透過有限的一些方式使用動態記憶體,這些方式會帶來許多好處,而且不會出現不可接受的風險。方法之一是只在程式啟動時使用動態分配獲取資源。這樣應用程式可以更自由地為需要的物件分配記憶體,但可以避免穩定狀態執行期間出現與碎片、泄漏和不確定性有關的問題。

對C++編程人員來說,瞭解動態記憶體分配的優缺點非常重要。C++提供了許多工具,最著名的是具備建構子和解構子的類別,它們能大幅消除記憶體泄漏事件。C++還能幫助你為每個類別定義operator new,為確定性、固定大小的分配方案提供方便高效的封裝方法。你甚至可以使作為operator new將元件暫存器放在記憶體映射的位置。

作者:Dan Saks

總裁

Saks & Associates公司





投票數:   加入我的最愛
我來評論 - 動態記憶體分配的優缺點分析
評論:  
*  您還能輸入[0]個字
*驗證碼:
 
論壇熱門主題 熱門下載
 •   將邁入40歲的你...存款多少了  •  深入電容觸控技術就從這個問題開始
 •  我有一個數位電源的專利...  •  磷酸鋰鐵電池一問
 •   關於設備商公司的工程師(廠商)薪資前景  •  計算諧振轉換器的同步整流MOSFET功耗損失
 •   Touch sensor & MEMS controller  •  針對智慧電表PLC通訊應用的線路驅動器
 •   下週 深圳 llC 2012 關於PCB免費工具的研討會  •  邏輯閘的應用


EE人生人氣排行
 
返回頁首