※本記事は広告を含みます。
メモリ領域解説シリーズ
本記事は「メモリ領域解説」シリーズの1つです。
PC向けの開発では、malloc は当たり前の存在です。
必要なときに必要な分だけメモリを確保できる、非常に便利な仕組みです。
しかし、組み込みの現場では次のように言われることがあります。
「なるべくmallocは使わない方がよい」
これは単にRAMが少ないから、という単純な理由ではありません。
組み込み特有のメモリ構造、長期稼働、そしてリアルタイム性が関係しています。
初心者さんPCだと malloc ってそんなに問題にならないですよね?



それはMMUがあるからなんだ



MMU?



仮想アドレスを物理メモリに変換してくれる仕組みだよ。これがあると、連続メモリじゃなくても確保できるんだ



じゃあマイコンだと?



MMUがないことが多いから、本当に連続したメモリが必要になる。だからmallocが不安定になるんだ
この違いを理解するために、まずMMUについて簡単に説明します。
MMUとは?
MMU(Memory Management Unit)は、
仮想アドレスを物理メモリに変換する仕組みです。
PCやスマートフォンのOSでは、各プログラムは独立した仮想メモリ空間を持っています。
この仮想アドレスを実際の物理メモリに変換しているのがMMUです。
これにより次のようなことが可能になります。
- プログラムごとに独立したメモリ空間
- 他のプログラムのメモリ保護
- 非連続メモリの連続利用(ページング)
mallocとMMUの関係
PC向けOSではMMUがあるため、mallocは仮想メモリ上で確保されます。
そのため、物理メモリが連続していなくても、大きな領域を確保できます。
一方、MMUを持たない組み込み機器では、mallocは実際の物理メモリから直接確保されます。
この場合、連続したメモリ領域が必要になるため、断片化による確保失敗が起こりやすくなります。
これが、組み込み開発でmallocが慎重に扱われる理由の一つです。
組み込みのメモリ構造と領域の役割
組み込みシステムでは、プログラムは主にFlashとRAMに分かれて配置されます。
ROM・FLASH・RAMの違いについてはこちらの記事で解説しています。
C言語のメモリ領域の全体概要についてはこちらの記事で解説しています。
組み込みシステムの典型的なメモリ構造


■ テキスト領域(.text)
プログラムの実行コードが格納される領域です。
関数本体や命令列がここに配置され、通常はFlashメモリ上に置かれます。
電源を切っても内容が保持されるため、RAMを消費しません。
CPUはこの領域から命令を読み取り実行します。
■ データ領域(.data)
初期値を持つグローバル変数や static 変数が配置されます。
例えば:
int counter = 10;
static int flag = 1;これらはコンパイル時に初期値が決まり、
起動時にFlashからRAMへコピーされます。
そのため、
- Flashに初期値分の領域が必要
- RAMにも実行用の領域が必要
という特徴があります。
■ BSS領域(.bss)
初期値を持たないグローバル変数や static 変数が配置されます。
int buffer[1024];
static int state;これらは起動時に0クリアされます。
.bss は初期値をFlashに保持しないため、
Flash消費は増えませんが、RAMは消費します。
大きな配列を置くと、ヒープやスタックの余裕が減る原因になります。
データ領域とBSS領域の違いについてはこちらの記事で解説しています。
データ領域とBSS領域の初期化を行うスタートアップコードについてはこちらの記事で解説しています。
■ ヒープ領域(Heap)
malloc によって動的に確保される領域です。
プログラム実行中にサイズが決まるため柔軟ですが、
- 実行時間が一定ではない
- 断片化が発生する
- Stackと衝突する可能性がある
というリスクがあります。
図のように、ヒープは低アドレス側から高アドレス側へ向かって拡張します。
■ スタック領域(Stack)
関数呼び出し時の戻りアドレスやローカル変数が配置される領域です。
関数が呼ばれるたびに積み上がり、
終了すると巻き戻されます。
通常は高アドレス側から低アドレス側へ向かって拡張します。
スタックサイズの見積もりを誤ると、
ヒープとの衝突やスタックオーバーフローが発生します。
スタック領域とヒープ領域についてはこちらの記事で解説しています。
C言語のスタックフレームについてはこちらの記事で解説しています。
重要なポイント
HeapとStackが同じRAMを奪い合う構造になっていること
です。
ヒープを増やせばスタックの余裕が減ります。
スタックを大きく確保すればヒープが圧迫されます。
この「奪い合い」が、設計判断を難しくしています。
なぜmallocは慎重に扱われるのか
1. RAM容量が限られている
例えばRAM 64KBのマイコンで、ヒープに16KBを割り当てたとします。
その中で可変長データを繰り返し確保・解放した場合、
将来的にどの程度断片化するかを正確に予測するのは困難です。
設計段階での見積もりが甘いと、運用後に破綻する可能性があります。
2. メモリリークは時間差で影響する
PCアプリケーションであれば、最悪の場合再起動すれば回復します。
しかし組み込み機器では、
- 長期間連続稼働する装置
- 電源断が困難な設備
- 現場で再起動できない機器
といった前提も珍しくありません。
free の漏れは、数時間ではなく数週間・数ヶ月かけて問題を顕在化させます。
この「時間差」が大きなリスクになります。
3. 断片化
断片化は特に厄介な問題です。
ヒープ断片化の例(空きはあるが連続領域がない状態)


空き容量の合計は十分でも、連続した大きな領域が確保できないことがあります。
テストでは問題なく動作していたのに、
長期運用の現場でのみ不具合が発生するケースもあります。
原因追跡が難しく、対処に時間がかかることも少なくありません。
4. リアルタイム性との相性
組み込みシステムでは、
- 制御周期が1ms
- 割り込み処理が高優先度
- 実行時間の保証が必要
といった制約が存在することがあります。
しかし malloc の実行時間は一定ではありません。
- 確保サイズ
- フリーリストの状態
- 断片化状況
によって処理時間が変動します。
つまり、実行時間が読めません。
リアルタイム制御において、実行時間が保証できない処理を組み込むことはリスクになります。
5. デバッグの難しさ
多くのマイコンにはMMUがありません。
そのためメモリ破壊が発生しても、
- 明確な例外が出ない
- セグメンテーションフォルトが発生しない
- 別の変数が壊れる
といった形で現れることがあります。
ヒープ絡みの不具合は再現しにくく、原因特定が困難になりがちです。
6. スタックとの衝突
ヒープだけでなく、スタック設計も重要です。
- 深い再帰呼び出し
- 大きなローカル配列
- RTOS環境でのタスクスタック不足
これらがヒープと衝突すると、予期しない動作を引き起こします。
ヒープの利用は、スタック設計にも直接影響します。
それでもmallocにはメリットがある
ここまで読むと、mallocは危険な存在に見えるかもしれません。
しかし当然ながらメリットもあります。
- 可変長データ構造が扱いやすい
- 必要なときだけ確保できる柔軟性がある
- モジュールの独立性を高められる
- メモリ効率が向上する場合もある
重要なのは、
「禁止すること」ではなく「制御すること」
です。
私が設計で意識していること
私は基本的に、
- 静的確保を優先する
- 最大使用量を見積もる
- スタックサイズに余裕を持つ
- リアルタイム制約を確認する
- 長期稼働を前提に設計する
という方針を取っています。
「今動くかどうか」ではなく、
「時間が経っても安定しているか」
を重視しています。
まとめ
mallocが慎重に扱われるのは感情論ではありません。
- RAMが限られている
- 長期稼働が前提
- リアルタイム制約がある
- デバッグが難しい
といった組み込み特有の条件の中で、リスクが顕在化しやすいからです。
そのため、組み込み開発では「動的確保より静的確保を優先する」
という設計思想が広く採用されています。
重要なのは、
メモリの性質と制約を理解したうえで設計判断を行うことです。
それが組み込み開発に求められる視点だと考えています。
この記事が参考になった方へ
メモリ領域については他の記事でも解説しています。
技術に関するご相談・開発・自動化ツール作成・記事執筆などのご依頼も承っています。
小さなご相談からでもお気軽にご連絡ください。









コメント