HeapAllocとLocalAllocの違いと選択基準
Windows APIで動的メモリを確保する場合、HeapAllocとLocalAllocはいずれも候補になります。しかし、両者は同じ目的の単純な別名ではありません。確保元となるヒープ、フラグの意味、解放方法、古い互換機能との関係が異なるため、コードの寿命や連携先に応じた選択が必要です。
特に新しくWindows向けプログラムを書くなら、プロセスヒープを明示的に扱えるHeapAllocが基本になりやすい一方、既存APIや古いコードとの互換性ではLocalAllocが指定されることがあります。Windows APIのメモリ管理を整理するには、関数名だけでなく、所有権とライフサイクルまで確認することが重要です。
それぞれの役割と背景
HeapAllocは、指定したヒープからメモリブロックを確保する関数です。通常はGetProcessHeapで取得したプロセスヒープを渡しますが、HeapCreateで作成した専用ヒープを使うこともできます。確保した領域はHeapFreeで解放し、別の解放関数を混在させてはいけません。
一方、LocalAllocはローカルヒープ用のAPIです。現在のWindowsでは、歴史的な互換性を保つために提供されている性格が強く、LMEM_FIXEDを使う場合は実質的に固定アドレスのメモリを確保します。LMEM_MOVEABLEではハンドルが返され、LocalLockで実アドレスを取得する仕組みになります。
固定ブロックを使う限り、LocalAllocの使用感はGlobalAllocと似ています。ただし、LocalAllocで確保したメモリはLocalFreeで解放する必要があります。確保関数と解放関数の組み合わせを崩すと、ヒープ破壊や予測不能な障害につながります。
API仕様から見た主な相違点
HeapAllocでは、確保サイズとHEAP_ZERO_MEMORYなどのフラグを指定します。ゼロ初期化が必要な場合は明示的にフラグを付けますが、フラグを付けなければ内容は未初期化です。LocalAllocのLMEM_ZEROINITも同じく、初期値を保証したい場合に指定します。
エラー処理にも注意が必要です。HeapAllocの失敗時はNULLを返し、LocalAllocも通常はNULLを返します。サイズ計算で整数オーバーフローが起きると、想定より小さい領域しか確保されず、後続の書き込みで脆弱性になるため、要素数と要素サイズを掛ける前に検査します。
関数ポインターや構造体を格納する場合は、対象プロセスのアーキテクチャも確認します。32ビットと64ビットではポインターサイズが異なるため、sizeofを使って必要量を算出し、固定長の数値型とアドレス型を混同しない設計が求められます。
ファイル選択ダイアログなど、別のWindows APIが返すメモリの扱いを調べる際にも、戻り値の所有者と解放方法を確認する習慣が役立ちます。APIの呼び出し手順を整理したファイル選択ダイアログの解説でも、構造体やバッファの扱いを読む際には同じ視点が必要です。
選択を左右する比較ポイント
次の比較では、一般的なWin32デスクトップアプリケーションでの使い分けを示します。LocalAllocのすべての歴史的挙動を網羅するものではなく、新規コードの判断材料として見ると分かりやすくなります。
| 比較項目 | HeapAlloc | LocalAlloc |
|---|---|---|
| 主な用途 | プロセス内の汎用メモリ確保 | 既存APIや互換コードとの連携 |
| 確保元 | プロセスヒープまたは専用ヒープ | ローカルヒープ |
| 解放関数 | HeapFree | LocalFree |
| 移動可能メモリ | 基本的に扱わない | LMEM_MOVEABLEでハンドル管理 |
| ゼロ初期化 | HEAP_ZERO_MEMORY | LMEM_ZEROINIT |
| 新規コードでの位置付け | 第一候補になりやすい | 要件がある場合に限定しやすい |
| スレッド安全性 | 同一ヒープの利用条件を確認 | API仕様と共有方法を確認 |
HeapAllocの利点は、専用ヒープを作成して特定用途のメモリをまとめられることです。大量の短命オブジェクトを一括管理したり、解放処理を簡素化したりする設計では、HeapCreateと組み合わせる意味があります。ただし、専用ヒープを作ったら、破棄までの所有関係を明確にしなければなりません。
LocalAllocは、古いライブラリ、既存のインターフェース、または明確にLocalFreeを要求するAPIと接続するときに選びます。単に「昔から使われているから」という理由で新規コードへ採用すると、移動可能メモリのハンドル管理や解放規則が不要に複雑になることがあります。
用途別に見る判断材料
メモリ確保関数を決めるときは、次の項目を先に洗い出すと、実装後の修正を減らせます。
- 呼び出し先が要求する確保関数と解放関数
- 確保したメモリを所有するモジュール
- 固定アドレスで扱う必要性の有無
- 確保回数、平均サイズ、最大サイズ
- 失敗時に継続できるかどうか
- 既存コードとのABI互換性
一般的なバッファ、可変長配列、内部処理用の構造体であれば、HeapAllocを使い、対応するHeapFreeを同じ所有者が実行する構成が分かりやすいでしょう。C++であればRAIIラッパーを用意し、途中 return や例外が発生しても解放漏れが起きないようにします。
LocalAllocを選ぶ場合は、LMEM_FIXEDとLMEM_MOVEABLEを混同しないことが大切です。LMEM_MOVEABLEで得たHLOCALは、ロックしている間の扱い、ロック解除のタイミング、再確保後のアドレス変化を意識する必要があります。単純な作業バッファなら、移動可能メモリを使う合理性があるかを再検討します。
安全な実装とデバッグの要点
確保と解放の対応表を、関数単位ではなくデータ構造単位で決めると管理しやすくなります。HeapAllocならHeapFree、LocalAllocならLocalFreeという組み合わせをコードレビューで確認し、別のDLLへポインターを渡す場合は、どのモジュールが解放するのかを明文化します。
また、バッファの長さと有効データ長を分けて保持し、終端のNULL文字を含めたサイズを計算します。文字コード変換では、バイト数と文字数を取り違えやすいため、MultiByteToWideCharなどの戻り値と確保サイズを照合します。
デバッグ時にはApplication Verifierやページヒープが有効です。解放済み領域への書き込み、二重解放、境界外アクセスを早期に検出できます。Visual Studioのメモリ関連診断も併用し、障害が発生した場所ではなく、破壊が起きた最初の書き込み箇所を探します。
既存資産との互換性を保つ方法
古いコードを現代化する場合、いきなり全てのLocalAllocをHeapAllocへ置換するのは危険です。呼び出し先がHGLOBALやHLOCALを要求していないか、DLL境界でメモリを受け渡していないか、構造体の仕様が固定されていないかを確認します。API仕様を調べる起点として、Windows関連情報をまとめた技術リファレンスサイトも参照できます。
置換するなら、まず確保と解放を一対にした小さなラッパーへ集約し、テストで動作を確認します。外部へ渡す値と内部だけで使う値を分離すれば、内部実装をHeapAllocへ変更しても、互換性が必要な境界だけ従来形式に残せます。
移行時には、関数名を変えるだけでなく、エラー処理、ゼロ初期化、アライメント、再確保の動作を比較します。特にLocalReAlloc相当の処理を使っていたコードでは、単純なHeapReAllocへの変更でポインターの有効性が変わる可能性があります。
設計で最後に確認したいこと
新規の内部処理では、所有権を明確にできるHeapAllocが扱いやすい選択になりやすいでしょう。専用ヒープが不要ならプロセスヒープを使い、必要な場合だけHeapCreateを採用すると、管理対象を増やしすぎずに済みます。
LocalAllocは廃止された関数という意味ではありませんが、互換性を担うAPIとしての性格を理解して使う必要があります。既存インターフェースがLocalFreeを指定しているなら、それに従うことが最優先です。解放関数を独自判断で変更してはいけません。
最終的には、確保元、フラグ、所有者、解放関数、失敗時の動作を一組として設計します。HeapAllocとLocalAllocの違いを覚えるだけでなく、メモリを誰がいつまで管理するかを記録することが、Windows APIで安全なコードを書くために最も重要です。