今のオペレーティングシステムでは複数のプロセスを同時に実行できる。それらのプロセスそれぞれに独立したメモリが割り当てられる。
「プロセスごとに仮想のメモリアドレスを持つ」ことで、実際のメモリの保存領域が隣り合っていなくてもまるっと見えるようになっている。(仮想メモリアドレスから実際の物理アドレスへのアクセスはページテーブルと呼ばれる階層型のデータ構造を使う)
確保される領域の大きさは、アドレス桁数通りで、基本的にはCPUのビット数に基づく。プロセスごとのメモリ空間は完全に独立している。WindowsもLinuxも1プロセスあたり最大128テラバイトまで割り当てられる。
小さい番地の方から、プログラム・スタティックな変数が置かれ、大きい番地にはカーネルから動的に割り当てられたメモリ領域が配置される。
ここまででプロセスができてから、プロセス固有のメモリ空間が用意され、仮想メモリ空間の中にプログラムが利用できるメモリのブロックができた。
ここからはシステムコールで「メモリブロックをOSに依頼して割り当ててもらう」必要がある。 syscall.Mmapによって、実行時に必要な動的メモリを確保する。
ここまで来て、プログラムが自由に使えるメモリが手に入った。ここからはコードから扱いやすい形式でメモリを確保・開放する必要がある。
一般にスタックは自動的に管理され、関数呼び出しとともにローカル変数が格納され、実行終了とともにメモリが解放される。高速で、メモリの割り当てと解放を自動で行うため、開発者がメモリを意識することはない。
ヒープは動的メモリ領域で、プログラムの実行タイミングで動的にサイズが変わるデータや大量データを扱う。スタックよりもはるかに大きなメモリ領域を利用できる。
まとめるとこんな感じである(言語によって違う
確保するメモリサイズ
スタック・・・固定サイズ
ヒープ・・・動的
メモリサイズが決まるタイミング
スタック・・・コンパイル時
ヒープ・・・実行時
メモリ割り当てを行うデータ型
スタック・・・プリミティブ型と参照
ヒープ・・・オブジェクトや配列、関数
Go言語のランタイム
Go言語のランタイムでは、変数をスタックに置くかヒープに置くかは自動的に決定される。このプロセスはコンパイル時に行われ、「エスケープ分析」と呼ばれる。
一般にスタックの方が速いので、スタックに載せようとするが、返り値で使おうとした場合など、宣言した関数のスコープよりも変数の寿命が長くなりヒープに配置される。
基本的には自動で使い分けられるため、ユーザはほぼ意識することはない。