🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

記憶體的動態配置(Dynamic Memory Allocation)是指在程式執行過程中根據需要分配記憶體的一種方法。在C語言中,malloccalloc,而在C++中則使用new運算子來實現這一功能。
然而,在嵌入式系統或即時系統的開發中,動態記憶體配置經常會被避開。
本文將探討其原因。

那麼,它是怎麼運作的呢?

遵循傳統的格言 「讀源碼」,我們來看看實際的源碼。作為最簡單的實現之一,我們將關注使用於元祖Arduino的針對AVR微控制器的malloc實現。

AVR用libc的malloc的實現

malloc的基本操作

malloc堆區域(用於動態配置的記憶體區域)中分配所要求大小的記憶體區塊。
堆的可用空間是以列表結構進行管理的。malloc會遍歷這個列表,找到適合要求大小的空閒記憶體區塊並進行分配,剩餘的部分則作為新的空閒區塊被加入列表中。

這裡簡單介紹了一下,實際上,存在為了尋找最佳空閒空間的更智慧的實現,詳細資訊請參見源碼。

free的基本操作

free用於釋放通過malloc分配的記憶體區塊。被釋放的區塊會重新加入堆的空閒區域列表中。
此外,如果存在相鄰的空閒區塊,則會將它們合併成一個更大的空閒區塊。

那麼為什麼會被嫌棄呢?

記憶體碎片化

透過動態的記憶體分配和釋放,堆區域可能被細分,導致可用的大連續記憶體區塊不足。

我們來看一個實例。元祖Arduino Duemilanove(搭載ATmega328P)擁有2KB的SRAM,去掉堆疊和其他區域後,堆可用的最大空間約為1800位元組。

首先,以下代碼可以正常運行。

// 分配1600位元組的記憶體
void* a = malloc(1600);
Serial.print("a = ");
Serial.println((uint16_t)a);

輸出:
a = 512

成功分配了1600位元組的記憶體。雖然佔用了SRAM的大部分,但並不成問題。

接下來,我們執行以下代碼。

// 分配1000位元組的記憶體
void* b = malloc(1000);
Serial.print("b = ");
Serial.println((uint16_t)b);

// 分配1位元組的記憶體
void* c = malloc(1);
Serial.print("c = ");
Serial.println((uint16_t)c);

// 釋放最初分配的記憶體(1000位元組)
free(b);

// 分配1200位元組的記憶體
void* d = malloc(1200);
Serial.print("d = ");
Serial.println((uint16_t)d);

輸出:
b = 512
c = 1514
d = 0

儘管實際上只用了1位元組,但1200位元組的記憶體分配失敗。這是因為堆區域碎片化如下所示:

+-------------------+
|                   |
|   1000位元組空閒  |
|(分配給b的區域)  |
|                   |
+-------------------+
|   1位元組使用中    |
|(分配給c的區域)  |
+-------------------+
|                   |
|    剩餘空閒區域    |
|    約800位元組     |
|                   |
+-------------------+

如您所見,並不存在1200位元組的連續空閒區域,因此malloc(1200)失敗。

在這種簡單的情況下處理起來還算容易,但在實際應用中,記憶體的配置和釋放會變得複雜,因此可能會產生難以預測的記憶體碎片化。

在當今的豐富環境中,虛擬記憶體和垃圾回收的存在使得這並不成為太大問題,但在資源有限的嵌入式系統中,記憶體碎片化卻是一個嚴重的問題。

無法預測的操作時間

如前面所述,mallocfree需要掃描堆,可能會進行相鄰區塊的合併,因此在堆狀態的不同時,操作時間可能會有很大變化。在即時系統中,需要在一定時間內完成處理,因此無法預測的操作時間是不可接受的。

記憶體洩漏

如果不小心忘記釋放使用malloc分配的記憶體,就會發生所謂的記憶體洩漏。在C語言的語法上並不會報錯,靜態分析工具也難以檢測,在小型記憶體中長時間運行的嵌入式系統中可能會造成致命的後果。

替代方法

為了迴避動態記憶體配置的問題,常用的幾種方法如下:

靜態記憶體分配

在程式編譯時就分配所需的記憶體。C語言使用static關鍵字或全局變數來實現。

批量記憶體分配

在程式啟動時(例如Arduino的setup()函數中)一次性分配所需的記憶體,之後不再使用mallocfree。這樣可以避免無法預測的記憶體碎片化或操作時間的變動。

池型記憶體管理

簡單來說,即是自己管理記憶體的方式。雖然麻煩,但這樣可以確保自己的運行行為。

總結

動態記憶體分配很方便,但在需要穩定運行的嵌入式系統或即時系統中,由於記憶體碎片化、無法預測的操作時間和記憶體洩漏等問題,經常會被避開。考慮替代方法,選擇最適合系統需求的記憶體管理方式是非常重要的。


原文出處:https://qiita.com/felis_silv/items/d7e7c84a6b712299102b


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝16   💬10   ❤️5
422
🥈
我愛JS
📝2   💬8   ❤️4
94
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付