こんな疑問にお答えします。
メインメモリのスタック領域・ヒープ領域・静的領域とは
前提としてプログラムが動く仕組みとコンピュータの各パーツの役割をざっくり把握しているとよりわかりやすいです。
コンピュータについてよく知らないよという方は以下の記事を読んでから進むのをおすすめします。
コンピュータの仕組み
-
【初心者向け】コンピュータとプログラムの仕組みを図解でわかりやすく解説
コンピュータってどんな仕組みで動いてるの?CPUとかメモリとかって聞くけど、どんな働きをしてるの?わかりやすく教えてほしい。 プログラムってパソコン上でどうやって動いてるの?初心者でも理解できるように ...
続きを見る
スタック領域・ヒープ領域・静的領域はすべてメインメモリ内に割り当てられる領域です。
メインメモリは単にメモリと言ったり、主記憶装置やRAM、揮発性メモリと呼ばれたりします。
C言語などでコーディングして作成したプログラムはexeファイルして保存されますよね。
exeファイルを実行するとメインメモリ内部は以下のように、CPUへの命令を保存する領域と変数値などのデータを保存する領域に分けられます。
CPUへの命令(処理)を保存する領域はテキスト領域と呼ばれます。
命令とは例えばa+bという演算をする、aに1を代入する、aとbの値を比較して結果によって次の処理を決める(if文)などといった処理のことです。
テキスト領域に書かれている命令をCPUが上から順番に読み込み実行することでプログラムが動作します。
データを保存する領域はデータの種類によってさらに静的領域、ヒープ領域、スタック領域の3つに分けられます。
領域 | 保存されるもの |
静的領域 | プログラム実行中にずっと存在する変数(グローバル変数やstatic変数) |
スタック領域 | 一時的に領域が確保・解放される変数など(ローカル変数、関数の引数など) |
ヒープ領域 | プログラム実行中に領域の大きさを決められる動的データ(malloc関数、new演算子など) |
グローバル変数やstatic変数はプログラムの実行中ずっと保持されます。
変数の種類については以下の記事で解説しています。
変数について詳しくはこちら
-
【C言語】変数のスコープとは?static・ローカル・グローバル変数の違い
C言語やC++を勉強しているとたまに出てくる変数のスコープって何?難しそうだからわかりやすく解説してほしい。 ローカル変数とグローバル変数とstatic変数って何が違うの? こんな疑問にお答えします。 ...
続きを見る
このような変数は静的領域にまとめて記憶されます。
ローカル変数など有効範囲が決まっていて一時的に使う変数はスタック領域に記憶されます。
変数の有効範囲についても上の記事で解説しています。
ある関数が呼び出されると関数内で宣言された変数がスタック領域に記憶され、関数の処理が終わると自動的に解放されるといった流れです。
解放された領域はまた別の関数の変数に使われます。
ヒープ領域はC言語ならmalloc関数、C++ならnew演算子といった特殊な機能を使った際に使われます。
malloc関数やnew演算子は確保する領域の大きさをプログラム実行中に変化させられるという特徴があります。
malloc関数やnew演算子の有効範囲はプログラマが決められます。
メインメモリ上に作られる領域については以降で詳しく解説します。
静的領域・スタック領域でのメインメモリの使われ方
静的領域・スタック領域では変数の型ごとに決まった大きさの領域が確保されます。
例えばC言語で整数(int)を宣言すると32ビット分のデータ領域が確保されます。
上の図ではマス1つあたりメモリセル1つを表しています。
ビットやバイト、メモリセルについて詳しくは以下の記事で解説しています。
詳しくはこちら
-
bit(ビット)とbyte(バイト)の違い、換算時の計算方法を解説
ビットとバイトってどう違うの?メガバイトとかギガバイトとか聞くけど何のことかよくわからない。 こんな疑問にお答えします。 記事の内容 ビット・バイト間の換算方法 ビットとバイトの違い 2進数とは? キ ...
続きを見る
メモリセル1つには1ビット分(0か1の2通り)のデータを保存できるので、32ビットはメモリセル32個分です。
整数の場合、確保された領域はさらに符号を保存する領域と値を入れておく領域に分けられます。
符号部分は0が入っていれば正の数、1ならば負の数を表します。
整数の値も2進数で保存されます。
例えば10進数の3(2進数では11)が代入されると、メモリセルには以下のように値が入ります。
8ビットをまとめて1バイトと呼びます。32ビットは4バイトに相当します。
何バイト分のメモリ領域が割り当てられるかは変数の型によって変わります。
intを宣言すると4バイトのメモリ領域が確保されますが、charの場合は1バイト、shortは2バイト、longは4バイトが確保されます。
配列を宣言した場合は(要素数)×(変数型のメモリ領域)分の領域が割り当てられます。例として整数の配列の場合は(要素数)×(4バイト)の領域が確保されます。
スタック領域の仕組み
「スタック」とは英語で積み重ねるという意味です。
スタック領域ではその名の通りデータを追加するときは次々と積み重ねていき、データを削除するときは最後に重ねたデータから消していきます。
このような仕組みをLIFO(Last In First Out、後入れ先出し)方式といいます。
LIFO方式はメモリ領域を効率的に使うのに好都合です。
以下ではスタック領域のメモリが確保されてから解放されるまでの流れをより詳しく解説します。
プログラムを実行するとまず「ここからここまでをスタック領域にする」というようにメモリの領域だけが確保されます。
この時点ではまだメモリ領域に変数は割り当てられません。
領域確保が終わって関数が実行されるとそのたびに関数内で宣言された変数に領域が割り当てられます。
ちょうど上の図で青やピンクの領域が追加されていくみたいにです。
確保されるメモリ領域の大きさは上で解説したように変数の型によって決まっています。
関数の実行が終了するとスタック領域のメモリが解放され、次の関数に使われます。
if文やfor文などの中で宣言された変数についても同様にスタック領域にメモリが確保、解放されていきます。
それでは以上の流れを具体例を使っておさらいします。
例:
#include <stdio.h> void print_num(int num2) { printf("num2=%d\n",num2); } void main() { char chara='A'; //① for(int num1=0; num1<2; num1++) //② { print_num(num1); //③ } //④ printf("chara=%c\n",chara); }
実行結果
num=0
num=1
chara=A
このコードにおけるスタック領域の使い方を簡略化して解説します。
スタック領域の大まかなイメージを掴んでいただければと思います。
まずプログラムを実行時するとスタック領域が確保されます。
main関数に入り、コード例の①の行に進むとローカル変数charaが宣言されます。
charaはchar型なので1バイト分の領域が確保され、そこにAという文字の情報が2進数の数字に置き換えられて保存されます。
実際はCPUのレジスタが空いていればローカル変数はそこへ保存されます。また、スタック領域には変数以外にもCPUの処理に使う値も保存されます。しかしここではスタック領域のイメージを掴んでもらうことが目的なのでこれらは省略しています。
②に進むと整数num1が宣言されるので、4バイト分の領域を確保し、値0を入れます。
③ではprint_num関数の引数num2の領域も確保します。
print_num関数は2回呼び出されるので、そのたびにnum2の領域確保、解放を繰り返します。
num1はfor文中では保持されますが、値は新しいループに入るたびに更新されます。
④でfor文を抜けるので、num1の領域も解放されます。
プログラム終了時にはすべてのメモリを開放して別のプログラムで使えるようにしておきます。
このようにスタック領域ではLIFO方式を採用することでメモリを余すことなく効率的に使えます。
静的領域・スタック領域・ヒープ領域の違い
静的領域
グローバル変数またはstatic変数を宣言するとプログラム実行時に静的領域へ値が保存され、プログラム終了まで保持されます。
変数がint型であれば4バイト、char型であれば1バイトと型ごとに決まった分の領域が確保され、そこに各々の値が入ります。
スタック領域やヒープ領域に記憶される変数はプログラム実行中に変化するのに対して、静的領域に記憶される変数はプログラムを通して不変(静的)です。
スタック領域
スタック領域にはグローバル変数、static変数、malloc関数(C言語)、new演算子(C++)以外の方法で宣言された変数が入ります。
具体的にはローカル変数や関数の引数などです。
ここでも変数の型ごとに決まった大きさの領域が確保されます。
こちらで解説したように、プログラム実行時にはスタック領域の場所だけが確保されます。
プログラムが進んで関数に入るとLIFO方式でメモリ領域が各変数に割り当てられます。
スタック領域の大きさはプログラマが指定できますが、特に指定しなければコンパイラが自動で大きさを指定してくれます。
ヒープ領域
C言語ならmalloc関数、C++ならnew演算子を使うとヒープ領域にデータが保存されます。
malloc関数とnew演算子には以下の特徴があります。
- プログラム実行時にはヒープ領域の場所だけが確保される
- 各変数に割り当てられるメモリ領域の大きさはプログラム実行中に変化する
- メモリ領域を解放するタイミングはプログラマが決める必要がある
例として上で見てきたローカル変数とmalloc関数を比較してみましょう。
ローカル変数としてint型の配列を宣言する際には要素数を事前に指定します。
関数に入ると4バイト×要素数分のメモリ領域が確保され、解放されるまで領域の大きさは変化しません。
関数を抜けるとメモリ領域は解放されます。
一方、malloc関数を使うと動的配列が作れます。
動的配列とは要素数をプログラム実行中に自由に増やせる配列のことです。
スタック領域は通常の配列のように事前にどれくらいのメモリ領域を使うか指定した場合のみ使えるので、動的配列はヒープ領域に別途格納します。
malloc関数で使ったメモリ領域を解放するにはfree関数を使います。
つまり解放するタイミングはプログラマが決めます。
例えばプログラム内で使うデータを外部から入力する場合、データの大きさが事前にわからないことがあります。
膨大な大きさになる可能性もあれば小さく済む可能性もあります。
そのようなデータを配列に格納するとなると配列の要素数をどうすべきか悩みますよね。
このとき万が一を考えて膨大な大きさの配列を用意するとメモリを使いすぎて重いプログラムになってしまいます。
そんなときにはmalloc関数が便利です。
malloc関数なら外部データの大きさに応じてメモリの使用量を変えられるので、不必要にメモリを使いすぎるのを防げるというわけです。
ローカル変数のように事前に使うメモリ領域の大きさを決めておくことを静的確保というのに対して、malloc関数のようにプログラムの実行中にメモリの使用量を変えることを想定して領域を確保することを動的確保といいます。
使われるメモリ領域の大きさが不変なら「静的」、可変なら「動的」と呼ばれます。
ヒープ領域はこのような動的確保に使われます。
ヒープ領域自体の大きさはプログラマが指定できますが、特に指定しなければコンパイラが自動で大きさを指定してくれます。
このように便利なヒープ領域ですが、プログラムの書き方によってはメモリリークを引き起こす可能性があるので注意が必要です。
メモリリークとはメモリ領域が解放されずにプログラム終了後も残ってしまうことです。
本来、malloc関数やnew演算子で確保したヒープ領域はそれぞれfree関数、delete演算子で解放しなくてはいけません。
しかしプログラマがfree関数やdelete演算子を記述し忘れたり、書いていても実行されていなかったりすると確保されたヒープ領域はプログラム終了後も残り続けます。
解放されなかったヒープ領域は別のプログラムで使用できないので、メモリリークが続くとメインメモリが埋まってコンピュータがダウンしてしまうのです。
ヒープ領域を使う際は注意しましょう。
スタック領域・ヒープ領域・静的領域の仕組みをより詳しく解説してくれる本
良いコードを書くにはメインメモリの使い方、つまりテキスト領域・スタック領域・ヒープ領域・静的領域の使い方を知っておくことが不可欠です。
本サイトではこれらについて簡単に解説しましたが、もっと詳しく知りたい方は以下の本も読んでみてください。
Kindleでも買えますが、個人的には紙の本で買うのがおすすめです。
ページを行ったり来たりしながら読む部分がありますし、会社で仕事をしながら読むこともあるかと思うので、紙の本の方が便利です。
まとめ
プログラムを実行するとメインメモリはCPUへの命令を保存するテキスト領域とグローバル変数などを保存する静的領域、ローカル変数などを保存するスタック領域、動的確保が可能なヒープ領域に分かれます。
スタック領域はLIFO方式でデータが保存することで効率よくメモリを使っています。
CPUはテキスト領域に保存された命令を上から順番に実行していき、必要に応じて静的領域・スタック領域・ヒープ領域に変数を保存します。
このようにしてプログラムは動いています。
一定レベル以上のプログラマはメインメモリの使い方を考えながらコーディングする能力が必要不可欠です。
本記事でメインメモリの使い方のイメージを掴んでいただければ幸いです。
C言語で行き詰まったら…
-
C言語・C++がわからない時に質問できるサイト・サービス5選
C言語を独学で勉強しているけど内容がイマイチわからない。もはや何がわからないのかもわからない。 C++のエラーが解決できない。ググってもわからない。わかる人に解説してほしい。 こんなお悩みにお答えしま ...
続きを見る