設定動態記憶體 動態記憶體分配時需要注意哪些坑?

2021-10-16 06:09:23 字數 3620 閱讀 5101

id:技術讓夢想更偉大

在使用c語言開發嵌入式產品的過程中,當使用到malloc函式時候都會有乙個爭議,「使用動態記憶體分配安全嗎?」,就連美**方在safety-critical的嵌入式航空電子裝置**中,也禁止動態記憶體分配,我們來細細分析下。

malloc的全稱是memory allocation,中文叫動態記憶體分配,用於申請一塊連續的指定大小的記憶體塊區域以void* 型別返回分配的記憶體區域位址。

當無法知道記憶體具體位置的時候,想要繫結真正的記憶體空間,就需要用到動態的分配記憶體,且分配的大小就是程式要求的大小。

用來分配所需的記憶體空間,並返回乙個指向它的指標。

//引數  :size -- 記憶體塊的大小,以位元組為單位

//返回值:指標 -- 指向已分配大小的記憶體

//       null -- 如果請求失敗

void *malloc(size_t size)

#include 

#include 

#include 

int main()

編譯結果如下,大家自行體會malloc的用法。

當我們了解了malloc的作用應用範圍以及用法之後,我們先看看它是怎麼實現記憶體分配的,在此我們需要先了解幾個概念。

為了簡單便捷,現代作業系統在匯程式設計序(或機器語言)層面,處理記憶體位址時,都是使用虛擬記憶體位址。這樣每個程序可以自己獨享一片2n位元組的記憶體,其中n是機器位數。例如在64位cpu和64位os下,每個程序的虛擬位址空間為2*64 byte

虛擬位址的作用主要是簡化程式的編寫及方便作業系統對程序間記憶體的隔離管理,由於在機器語言層面都是採用虛擬位址,作業系統會將虛擬記憶體和實際的物理記憶體進行對映,cpu晶元上叫做儲存器管理單元(memory management unit,mmu)的專用硬體,利用存放在主存中的查詢表來動態翻譯虛擬位址,才能實現對真實記憶體資料的操作。

在現代作業系統是以頁(page)為單位。乙個記憶體頁是一段固定大小的連續記憶體位址的總稱。

上面是虛擬記憶體位址,下面是物理記憶體位址。由於頁大小都是4k,所以頁內都是用低12位表示,而剩下的高位址表示頁號。

mmu對映單位並不是位元組,而是頁,這個對映通過查乙個常駐記憶體的資料結構頁表來實現。現在計算機具體的記憶體位址對映比較複雜,為了加快速度會引入一系列快取和優化。下面給出乙個經過簡化的記憶體位址翻譯示意圖。

在已經對映的記憶體空間結尾有乙個break指標,這個指標下面是對映好的記憶體,可以訪問,上面則是未對映的訪問,不能訪問。可以通過系統呼叫sbrk(位移量)確定brk指標的位置,同時返回brk指標的位置,達到申請記憶體的目。brk(void *addr)系統呼叫可以直接將brk設定為某個位址,成功返回0,不成功返回-1。而rlimit則是限制程序堆記憶體容量的指標。

malloc採用推進brk指標來增加堆的有效區域來申請記憶體空間分配記憶體,維護乙個記憶體空閒鍊錶,當申請記憶體空間時,搜尋記憶體空閒鍊錶,找到適配的空閒記憶體空間,然後將空間分割成兩個記憶體塊,乙個變成分配塊,乙個變成新的空閒塊。如果沒有搜尋到,那麼就會用sbrk()才推進brk指標來申請記憶體空間。

這其實要從malloc和free的設計上考慮,通常,它們是基於列表分配器演算法將記憶體池組織到單個鍊錶中的連續位置,使用分配器來管理該鍊錶,實際上就是尋找空閒位置。

但是在極端的safety-critical系統中,malloc常常極其不可**,在多核系統上進行多執行緒開發時是個難題,具體有以下幾點。

嵌入式的記憶體就只有幾十k到幾百k,程式在執行時向系統申請記憶體使用,在使用完畢後,需要顯式的釋放,不然後果很嚴重,在多次申請複雜的邏輯開發時,這就要求程式設計師對動態分配的記憶體很了解

在c語言中的malloc進行的動態記憶體分配和嵌入式系統中使用到堆區的記憶體分配會產生記憶體碎片,例如

char  *p;

if(p=char* malloc(0)==null)

else

實際上最終出現的並不是null,而是not null這就說明了進行動態記憶體分配的時候產生了記憶體碎片

所有的記憶體分配必須起始於可被 4、或8 或16 整除的位址,或者因為mmu的分頁機制的限制,決定記憶體分配演算法僅能把預定大小的記憶體塊分配給客戶。

假設在請求乙個17位元組的記憶體塊時,它可能會獲得20位元組、24位元組等稍大一點的位元組,因此由所需的大小需要四捨五入,而產生的多餘空間就叫內部碎片。

頻繁的分配與**物理頁面會導致大量的、連續且小的頁面塊夾雜在已分配的頁面中間,就會產生外部碎片。

分配出去的內存在使用之後沒有釋放掉,沒有**,長此以往,會造成沒有足夠的記憶體可以分配。一般表現為執行時間越長,占用的記憶體越多,最終導致系統奔潰。

所以在進行硬體記憶體比較小的外圍開發的時候,一定要避免記憶體洩漏,合理的使用記憶體空間,才能更好的發揮硬體的作用。

在繼續使用malloc和free的情況下

在實際應用中,我們可以試著把連續的大塊記憶體按分割槽來管理。每個分割槽中包含整數個大小相同的記憶體塊。如圖所示:

利用這種機制,就可以得到和釋放固定大小的記憶體塊。這樣記憶體的申請和釋放函式的執行時間就是確定的了,但是特定的記憶體塊在釋放時,必須重新回到它原本屬於的記憶體分割槽。

free函式其實就做了一件事:斬斷指標變數和這塊記憶體的對應關係,在使用free(p)函式記憶體釋放後,指標變數p本身儲存的位址並沒有改變,那我們必須需重新把p的值變為null:即p = null

盡量避免使用malloc時,我們可以自定義一套本地執行緒記憶體分配器,基於棧的分配器,以及基於本地執行緒的分配器,通過為每個執行緒分配特定的記憶體池來避免衝突

在嵌入式系統中,並不是說不使用malloc()和free()管理記憶體,而是說在使用時需要讓我們的**更具**性,避免不必要的未知bug產生。

protobuf:一種更小、更快、更高效的協議

在smt32程式hex檔案中加入韌體版本資訊

動態記憶體分配

在c 中建立乙個物件時,我們必須要為這個物件申請一塊記憶體,而且要用建構函式對這塊記憶體進行初始化。c 中的new和delete相對於c的庫函式malloc和free在這方面有很大的優勢,所以我們主要講的是運算子new和delete。當用new來建立乙個物件時,它會自動在堆裡為物件分配記憶體並且為這...

動態記憶體分配

為什麼使用動態記憶體分配?c語言中的一切操作都是基於記憶體的 變數和陣列都是記憶體的別名,如何分配這些記憶體由編譯器在編譯期間決定 定義陣列的時候必須指定陣列唱的 而陣列長度是在編譯期就必須決定的 需求 程式執行的過程中,可能需要使用一些額外的記憶體空間 malloc和free malloc和fre...

動態記憶體分配

c的儲存類別有4種 自動的 auto 靜態的 statics 暫存器的 register 外部的 extern 全域性變數時分配在記憶體中的靜態儲存區 靜態區域性變數屬於靜態儲存類別,在靜態儲存區內分配儲存單元,是在編譯時賦初值的,只賦初值一次,在程式執行時它已有初值,以後每次呼叫函式時不再重新賦初...