メモリ管理
プログラムがメモリを割り当て・解放する仕組み
メモリ管理方式
| 方式 | 仕組み | 言語例 | 特徴 |
|---|---|---|---|
| 手動管理 | malloc/free | C, C++ | 高速、制御可能 |
| GC | 自動回収 | Go, Java, Python | 安全、簡単 |
| 所有権 | コンパイル時解決 | Rust | GCなしで安全 |
| ARC | 参照カウント | Swift, ObjC | 予測可能 |
スタック vs ヒープ
スタック
- • 関数呼び出しで自動割り当て
- • LIFO(後入れ先出し)
- • 高速なアクセス
- • サイズが固定(コンパイル時に決定)
- • ローカル変数、関数引数
ヒープ
- • 明示的に割り当て/解放
- • 任意の順序でアクセス
- • スタックより遅い
- • 動的サイズ
- • 動的配列、オブジェクト
1 // Go - エスケープ解析 2 func stack() int { 3 x := 42 // スタックに割り当て 4 return x // 値コピーで返す 5 } 6 7 func heap() *int { 8 x := 42 // ヒープに割り当て(エスケープ) 9 return &x // ポインタを返すので関数終了後も必要 10 } 11 12 // go build -gcflags="-m" で確認可能
ガベージコレクション(GC)
使われなくなったメモリを自動的に検出し、解放する仕組み。
マーク&スイープ
1. ルートから到達可能なオブジェクトをマーク
2. マークされていないオブジェクトを解放
世代別GC
オブジェクトを世代で分類。若い世代を頻繁に、古い世代をまれに回収。 ほとんどのオブジェクトは短命という仮説に基づく。
Go のGC
並行マーク&スイープ。低レイテンシを重視(STW時間を最小化)。 GOGC環境変数でチューニング可能。
Rustの所有権システム
コンパイル時にメモリ安全性を保証。GCなしでメモリリークやダングリングポインタを防ぐ。
1. 各値には所有者が1つだけ
2. 所有者がスコープを外れると値は破棄
3. 所有権は移動(move)または借用(borrow)できる
1 // Rust - 所有権 2 fn main() { 3 let s1 = String::from("hello"); // s1が所有 4 let s2 = s1; // 所有権がs2に移動 5 // println!("{}", s1); // エラー!s1は無効 6 7 let s3 = String::from("world"); 8 let len = calculate_length(&s3); // 借用(参照を渡す) 9 println!("{} has {} chars", s3, len); // s3はまだ有効 10 } 11 12 fn calculate_length(s: &String) -> usize { 13 s.len() 14 } // sはスコープを外れるが、所有していないので何も起きない
参照カウント(ARC)
オブジェクトへの参照数をカウント。ゼロになったら解放。 循環参照に注意(弱参照で対処)。
1 // Rust - Rc (Reference Counted) 2 use std::rc::Rc; 3 4 fn main() { 5 let a = Rc::new(vec![1, 2, 3]); 6 println!("count: {}", Rc::strong_count(&a)); // 1 7 8 let b = Rc::clone(&a); // 参照カウント増加 9 println!("count: {}", Rc::strong_count(&a)); // 2 10 11 { 12 let c = Rc::clone(&a); 13 println!("count: {}", Rc::strong_count(&a)); // 3 14 } // cがスコープ外 → カウント減少 15 16 println!("count: {}", Rc::strong_count(&a)); // 2 17 }
メモリ関連の問題
メモリリーク
使わないメモリが解放されない。GC言語でも循環参照で発生可能。
ダングリングポインタ
解放済みメモリを参照。Rustは所有権で防止。
バッファオーバーフロー
境界を超えたメモリアクセス。Go/Rustは境界チェック。
データ競合
複数スレッドからの同時アクセス。Rustは借用チェッカーで防止。
Goのメモリ最適化
1 // sync.Pool - オブジェクトの再利用 2 var bufferPool = sync.Pool{ 3 New: func() interface{} { 4 return new(bytes.Buffer) 5 }, 6 } 7 8 func process() { 9 buf := bufferPool.Get().(*bytes.Buffer) 10 defer func() { 11 buf.Reset() 12 bufferPool.Put(buf) 13 }() 14 // bufを使用 15 } 16 17 // スライスの事前アロケーション 18 slice := make([]int, 0, 1000) // 容量1000を事前確保 19 for i := 0; i < 1000; i++ { 20 slice = append(slice, i) // 再アロケーションなし 21 }