スタックメモリーとは
スタック(stack)メモリーとは関数の引数や戻り値、ローカル変数に関数の呼び出し元へ戻るためのアドレスを保存するために用いられます。スタックメモリーは目につきにくい存在ですが、RAMの一部を専有しておりその容量には制限があります。Microsoft WindowsやMac OS、LinuxなどではRAM容量を気にすることはそうないでしょう。しかしマイコンのRAM容量はとても少ないのです。このため組み込みソフト開発者は、開発する製品の規模に合わせて適切なスタック領域を確保し、運用する必要があります。
メモリー領域
メモリー領域は下図のように、①テキスト領域、②静的領域、③ヒープ領域、④スタック領域に分かれます。このうち①がROMに、②~④がRAMに格納されます。このうち特異なのが今回のテーマであるスタック領域と、それに近いものとしてヒープ領域があります。 スタック領域とヒープ領域は、それぞれ使用するほどRAMを専有していき、領域を超えるとオーバーフローによりメモリー破壊が発生します。メモリー破壊は原因の特定が困難な不具合の1つで、最大限の注意が必要です。しかしそれを恐れてスタック領域やヒープ領域を多くとると、RAM不足で変数の確保が難しくなってしまいます。
- ヒープは動的変数(C言語のmalloc)を使う場合に必要な領域です。動的メモリーの使用は非推奨であり、ここでは説明を省略します。
- 処理系によっては、ビルド時にスタックオーバーフローを指摘してくれるものもあります。指摘された場合はスタック領域のサイズを大きくするか、スタック使用量の削減を試みてください。
メモリー領域の使われ方
実際のメモリー領域の使われ方は下図のようになります。
見てのとおり、関数の引数や関数内のローカル変数はスタック領域に確保されます。したがってスタックメモリーを節約するためには、次の点に気を付ける必要があります。
- 構造体を引数で渡す際は、値渡しではなくポインターで渡す。
- サイズの大きい配列を関数内で宣言しない。
どうしても必要な場合は、static変数またはグローバル変数にすることを検討してください。 - 再帰関数を使用しない。または関数を深くしない(関数の中で関数を呼び出す)。
- サイズの大きい引数やローカル変数を採用すると、スタック領域の専有と解放に掛かるオーバーヘッドが大きくなります。処理の遅延が発生するという意味でも、サイズの大きい引数やローカル変数の使用は避けるべきです。
- フレームポインターはベースポインターとも呼ばれ、呼びされた関数の起点となるアドレスを表します。ローカル変数はこのフレームポインターを起点に呼び出されます。
- オーバーヘッドとはコードの実行に際して、間接的に発生する処理を表します。今回の場合は、関数を呼び出して関数内の処理を実行するのが本来の処理。関数を呼び出すためにはスタック領域に引数やローカル変数枠を確保するのが間接的な処理となります。
スタックの構造
スタックの構造は後入れ先出し(LIFO:Last In First Out)です(下図 (a))。箱に入れたのとは逆順に、最後に箱に入れたものを最初に取り出します。スタックにデータを積むことをプッシュ、スタックからデータを取り出すことをポップと呼びます。
また対とるなるものとしてキューがあります。キューの構造は先入れ先出し(FIFO:FIrst In First Out)です(上図 (b))。