mallocを避ける設計
組込みソフトウェアでmallocを多用すると、メモリの断片化、実行時間の揺らぎ、メモリリークが顕在化しやすくなります。安全性とリアルタイム性が求められる現場では、静的確保とメモリプールを組みあわせて動的確保を最小限に抑える設計が一般的です。本記事では、断片化を防ぐ実装方針を整理しましょう。
なぜ組込みでmallocが問題になりやすいのか
組込みでは動的メモリ確保が3つのリスクを抱え込みます。
一つ目は、メモリの断片化です。メモリの確保と解放を繰り返すとメモリの断片化が進み、「細かいメモリの空き領域が多数ある」状態になります。メモリ容量が限られる組込みシステムでは、メモリの空き領域はあるのに連続したメモリ領域が取れず、メモリの確保に失敗することも珍しくありません。
次に、malloc/freeの内部処理は実行時間が一定でないことが挙げられます。実行時間が許容範囲を超えるケースがあり得るため、リアルタイム要件との相性は良くありません。
三つ目はメモリリークです。メモリの解放漏れが長期間で累積し、最終的には動的に割り当て可能なメモリ容量が無くなってしまう現象です。MISRA C:2023ではディレクティブ4.12で動的メモリ確保の使用を原則禁じています。
代替設計1:静的確保を優先する
動的確保のリスクを根本から避ける手段が静的確保です。使用量が事前に見積もれる領域は、グローバル変数や静的配列としてリンク時に配置を確定させます。運用中の確保処理が不要となり、メモリの最大使用量がビルド時に把握できる点も大きな利点でしょう。
欧州の車載ソフトウェアに関する標準規格「AUTOSAR Adaptive Platform」の一般要件にも、初期化フェーズ後はメモリの動的確保を必要としない設計が求められると記されています。
代替設計2:メモリプールを使う
すべてのメモリを静的に確保できない場合の現実解がメモリプールです。起動時に確保した固定領域をブロック単位で管理し、運用中はそのプール内だけで割り当てと返却をおこなう方式で、断片化を構造的に防ぎます。
固定長メモリプールを選ぶ条件
オブジェクトサイズがそろっているか、最大サイズで統一しても無駄が許容できる場合は固定長プールが適しています。ブロックサイズが均一なため、確保と返却が単純なリスト操作で完結し、原理的に断片化が発生しません。
実行時間が完全に一定で、最悪実行時間の解析もしやすい点が現場で評価されます。
可変長メモリプールを使うときの注意点
可変長メモリプールは、任意のサイズのメモリブロックを扱えます。柔軟性が高く、うまく使えば必要なメモリの消費を抑えられる反面、断片化と管理オーバーヘッドがあります。確保サイズが多様だと内部断片化や外部断片化が再発し、空きブロックの検索や結合処理で実行時間も変動することが避けられません。
解放順序が不規則だと、長期稼働で性能劣化を招くことが知られています。可変長メモリプールは初期化時にまとめて確保し、運用中の頻繁な解放は避ける運用が推奨されています。適用範囲を明文化しておくと、安心して運用できるでしょう。
確保失敗時の共通ルール(待機/エラーで終了/縮退)
固定長と可変長のどちらを使う場合でも、メモリプールを確保できない場合の挙動を設計段階で決め、コード全体で統一しておく必要があります。選択肢は、待機、エラーで終了、縮退の3つに分けて考えるのが基本です。
タイマや割込み駆動の処理は待機を選べないため、「エラーで終了」が現実的です。一方で医療機器や車載システムなど、高い安全性が求められる領域では、縮退運転が選ばれるケースが多くなっています。
それでもmallocを使う場合の限定運用
完全な排除が難しい現場では、mallocを使う場所と時間を限定する運用ルールが欠かせません。基本ルールはNASAやJPLの規約と同じで、動的確保は初期化フェーズに限定し、運用中の確保と解放は禁じる運用が定石です。
初期化時の一括確保なら、安定状態に入った段階で断片化の影響は固定され、実行時間の変動も発生しません。注意したいのは、購入し使用しているサードパーティライブラリやCランタイムが内部でmallocを呼ぶケースもあるため、ラッパー関数による検出や、リンカ設定で該当シンボルをエラーにする対策が現実的でしょう。
動的確保を例外として扱う設計姿勢が、断片化を抑え、組込み機器の長期安定稼働を支えます。


