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