変数を宣言すると、その値はどこに保存されるのでしょうか。
「メモリです」と答えるのは半分正解ですが、十分ではありません。同じメモリでも、スタックに置かれる値とヒープに置かれる値では、そのライフサイクルがまったく異なります。
スタックオーバーフローというエラー名、クラスと構造体の性能差、ARCが必要な理由まで、すべてこの2つの領域を知れば説明できます。
この記事では、スタックとヒープの動作、速度差が生じる理由、Swiftで何がどこに置かれるのかを整理します。
ここで指すのはメモリ領域です。LIFOデータ構造はSwiftのスタックとキューで、優先度付きキューを実装する木構造はヒープと優先度付きキューで別途扱います。
まず要点をまとめます。
- スタック:関数呼び出しで積まれ、リターンとともに消える自動管理領域。高速。
- ヒープ:実行時に必要なサイズだけ借りて使う動的領域。柔軟ですが管理コストがかかる。
- 速度差の本質は「割り当て・解放の方法」と「誰が後片付けをするか」です。
- Swiftでは、構造体はおおむねスタックに、クラスのインスタンスはヒープに置かれます。
スタック:積み重ねた皿
スタックは関数呼び出しの情報を積む領域です。名前どおり皿を積むように、最後に置いたものを最初に取り出すLIFO(後入れ先出し)構造です。
関数を呼び出すと、ローカル変数、引数、リターンアドレスがひとまとまりのスタックフレームとして積まれます。関数がリターンすると、そのフレーム全体が消えます。
ここにスタックの強みがあります。
割り当てが高速です。スタックポインターを上へ動かせば完了します。空き領域を探す必要はありません。
解放も無料です。関数が終わったらポインターを戻すだけで済みます。誰が片付けるか悩む必要もありません。
ただし制約があります。サイズはコンパイル時に決まっている必要があり、関数が終われば必ず消え、全体の上限も小さい(通常数MB)ことです。終了条件のない再帰関数が自分自身を呼び続けると、フレームが上限を超えます。それがスタックオーバーフローです。
ヒープ:広いが管理が必要な倉庫
ヒープは実行時に必要な分だけ借りて使う領域です。サイズを事前に知らなくてもよく、関数終了後も残り、容量もはるかに大きくなります。
その代わり、管理コストがかかります。
割り当ては相対的に遅くなります。要求サイズに合う空き領域を探す必要があり、複数スレッドが同時に要求すれば調整も必要です。
解放は誰かが責任を負う必要があります。関数終了後も消えないため、「もう使わない」と判断する役割が必要です。Cではプログラマーがfreeを直接呼び、Javaではガベージコレクターが定期的に掃除し、SwiftではARCが参照カウントを数え、0になった瞬間に解放します。
判断を誤ると2つの問題が起きます。解放が遅すぎればメモリーリーク、早すぎれば解放済みメモリーへのアクセス(ダングリングポインター)です。
Swiftでは何がどこに置かれるのか
Swiftの公式ドキュメントやWWDCセッションでは、次の構図が繰り返し強調されています。値型(構造体・列挙型)はスタックに、参照型(クラス)はヒープに割り当てられるのが基本です。
struct PointStruct { var x, y: Double }
class PointClass { var x = 0.0, y = 0.0 }
func run() {
let a = PointStruct(x: 1, y: 2) // スタックフレーム内に丸ごと
let b = PointClass() // ヒープに割り当て、スタックには参照だけ
}
構造体aは値が丸ごとスタックフレームに入ります。関数終了時にフレームとともに消えるため、参照カウントも不要です。
クラスインスタンスbはヒープに置かれ、スタックにはアドレスだけが置かれます。複数箇所から参照できるため、ARCがカウントを管理します。
Appleが「まず構造体を検討する」よう勧める性能面の根拠がこれです。ヒープ割り当て、参照カウント、ロックのコストをまとめて省けます。
ただし「構造体=必ずスタック」ではありません。クラスのプロパティになった構造体はクラスとともにヒープに置かれ、StringやArrayのような型は外見こそ構造体ですが、実データのバッファーをヒープに持ちます。「構造体はスタックに置ける条件を備えている」と理解するのが正確です。
面接でよくある追加質問
「なぜスタックはヒープより速いのですか?」 — スタックはポインターの移動だけで割り当て・解放が終わりますが、ヒープでは空き領域の探索と解放管理(参照カウント・GC)が必要だからです。
「なぜスタックオーバーフローが起きるのですか?」 — スタックの上限は小さいため、深い再帰や巨大なローカル変数でフレームが上限を超えると発生します。
「ローカル変数は必ずスタックに置かれますか?」 — いいえ。ローカル変数でも参照型なら、本体はヒープにあります。スタックに置かれるのは参照だけです。
まとめ
- スタックは関数呼び出し単位で自動的に積まれ、消える領域。ポインター移動だけで管理されるため高速。
- ヒープは実行時にサイズを決めて借りる領域。柔軟ですが、割り当て探索と解放管理のコストがかかる。
- 解放の責任:スタックは自動、ヒープは言語ごとに異なる(Cは手動、JavaはGC、SwiftはARC)。
- スタックオーバーフローは、深い再帰などでスタックの上限を超えると発生するエラー。
- Swiftでは構造体はおおむねスタック、クラスはヒープ。これが値型を優先する性能上の根拠です。
- ただしクラス内の構造体はヒープに置かれ、String・Arrayは内部バッファーをヒープに持ちます。

