我發現許多嵌入式軟件開發人員都提出了一個特別有趣的話題,那就是動態內存分配——在需要時獲取內存塊,這種看似簡單的常規操作帶來了大量問題。這些并不局限于嵌入式開發——許多桌面應用程序都會出現內存泄漏,影響性能,并可能導致系統重新啟動。但是,我擔心嵌入式開發環境。
通常不建議將malloc()用于嵌入式應用程序的原因有很多:
1.該函數通常不可重入(線程友好),因此在實時操作系統中使用它可能具有挑戰性。
2.它的性能是不確定的(可預測的),因此分配內存塊所需的時間可能非常可變,這在實時應用程序中是一個挑戰。
3.內存分配可能失敗。
雖然這些都是有效的觀點,但它們可能并不像看上去那么重要。
只有從多個線程調用函數時,才存在可重入性問題。編寫可重入的malloc()函數是完全可行的,但也可以使用標準版本,使重入變得不必要。只需將所有內存分配活動本地化為單個任務。你甚至可以創建一個任務,其唯一功能是動態內存分配;其他任務只需發送一條消息,請求分配或釋放內存塊。
決定論并非總是必需的。非實時應用程序是實時的,非實時應用程序并不一定要求其操作的所有部分都具有確定性。
分配失敗可能是一個問題,但它是可以管理的。如果無法分配請求的內存,則malloc()函數將返回空指針,嵌入式開發人員必須檢查此響應并采取適當的措施。如果故障是由于內存耗盡造成的,則很可能存在設計缺陷—沒有為堆分配足夠的內存。然而,分配失敗的一個常見原因是堆碎片;有足夠的可用內存,但它不在連續區域中。這種碎片的產生是因為內存以隨機方式分配和釋放,導致內存的分配區域和空閑區域。
盡管它不可預測,malloc()還有另一個問題——它的速度往往相當慢。這并不奇怪,因為函數的功能相當復雜。基于塊的分配器的內在簡單性非常有效地解決了這個問題。
但是,如果應用程序在不可預測的時間確實需要隨機大小的內存塊,該怎么辦?
實現這種靈活性,同時避免碎片和不確定性的一種方法是構建一個分配器,根據請求的內存塊大小從多個“池”中選擇塊。為池選擇塊大小的一個好方法(如果你事先不知道將需要的塊大小)是使用幾何級數,如16、32、64、128字節。然后,分配將如下所示:
顯然,有些分配會非常有效:16字節池中有16個字節,來自32字節池的31字節;來自16字節池的9字節;來自128字節池的65字節。總的來說,這些低效率對于速度、決定論和消除碎片化的好處來說只是一個很小的代價,可以提高嵌入式開發效率。