メモリマップの読み方
組み込み開発の現場では、プログラムや変数がROM・RAMのどこに載るかを示すメモリマップが欠かせません。.dataと.bssはどちらも実行時にRAMを使いますが、初期値の有無が異なるため、配置とROM消費が変わります。
この記事では、.dataと.bssの違いを起点に、ROM/RAM不足と実装時の注意点を解説します。
メモリマップを組み込みで読むべき理由
組み込み開発では、プログラムや変数がROMやRAMのどこに割り当てられるか、ビルド時にあらかじめ決まっています。リンク後に出力されるメモリマップは、各セクションのROM/RAM使用量やセクション構成を確認する手がかりになります。
ROMには.text・.rodata・.dataの初期値、RAMには.data・.bss・.heap・.stackが割り当てられる構成が一般的です。
- .textセクション:CPUが実行する命令(関数の本体など)
- .rodataセクション:読み出し専用の定数データ
- .dataセクション(ROM側):初期値つき変数の元データ
- .dataセクション(RAM側):初期値つき変数の実体
- .bssセクション:初期値なし変数の領域
- .heapセクション:動的確保(mallocなど)で使われる領域
- .stackセクション:関数呼び出しで増減する領域
起動時にスタートアップ処理が実行されると、.dataをROMからRAMへコピーし、.bssをゼロクリアしてからmain関数へ進みます。配置はリンカスクリプトで指定され、メモリマップはそのリンク結果を一覧で示したものです。.dataと.bssを理解すればマップが読め、ROM消費(初期値)とRAM消費を分けて追跡できます。
.bssと.dataの違いを理解する
.dataと.bssの違いは、グローバル変数やstatic変数に初期値があるかどうかです。.dataには初期値つきの変数が入り、値はROM(FLASH)に保存され、起動時にRAMへコピーされます。.bssには初期値なしのグローバル変数やstatic変数が入り、ROMには載せず起動時にゼロクリアします。
どちらも実行時はRAM上に存在するため、.dataだけがRAMを使うと考えるのは間違いです。電源投入直後のRAMは内容が保証されないため、スタートアップによる初期化が必須となります。初期化漏れは、不具合の「よくある」原因となります。実装時に忘れないよう、チェックしましょう。配置例として、初期値ありは.data、なしは.bss、constは.rodataとなることが多いです。
なぜ分かれているのか(ROM / RAMと起動処理)
.dataと.bssが分かれる主な理由は、ROM容量の節約と、起動時の初期化効率化にあります。たとえば大きな配列でも、.bssならROMに0は並べませんが、.dataに入ると0がROMに保存されます。
.dataはROMからRAMへコピーし、.bssはRAMを0で埋める、と手順を分けることで、起動時の流れが追いやすくなるでしょう。マップファイルでは.dataと.bssが別セクションで表示され、ROMとRAMの消費量を切り分けて確認できます。領域の違いを理解した上でマップを読むことが重要です。
RAM不足・ROM不足をどう切り分けるか
メモリ不足の切り分けでは、まずマップファイルで.dataと.bssのどちらが増えたかを確認します。.dataが増えると、初期値データ分のROM使用量が一気に増え、RAM上の.dataサイズも大きくなります。一方で、.bssが増えると主にRAMを圧迫し、大きなバッファや配列の定義がRAM不足の原因となります。
マップではROM消費(.dataの初期値)とRAM消費(.data+.bss)を別々に読み取れます。.dataはROMとRAMの両方に効き、.bssは主にRAMへの影響と覚えると、対策の優先順位を決めやすくなるでしょう。.data領域と.bss領域の意味がわかると、リンクエラーや起動不安定の原因追跡にもつながります。
実装でハマりやすいパターン
実装の段階では、変数の初期化の書き方によって.dataが意図せず増える場合があります。大きな配列を初期値なしで定義すれば.bssに割り当てられ、起動時はゼロクリアで初期化されます。同じ配列でも明示的に= {0}と書くと.dataに割り当てられ、ROMへ初期値が保存される場合があるでしょう。配置はコンパイラや最適化に依存するため、変更後はマップでセクション増の加を確認してください。大きな配列はROM削減のため初期値を書かず.bssに入れる判断が多く見られます。小さな配列では、可読性のために明示的に= {0}と書くこともよくあります。


