レガシー Windows API(概要)について

ここで紹介しているAPIは、Windows95、Windows NT3.5時代のAPI群であり、最新のAPIは含まれていないことに注しして欲しい。なお、当時の解説書は現在よりもより詳細に解説しているため、参考になるはずである。

  • レイアウトを変換する際、崩れているページがあります。
  • 概要およびSampleコードはありますが、APIの解説はありません。

ご要望があれば旧版のWin32 API(HELP形式)をお譲りします。問い合わせフォームより申し込みください。

Windows API メモリ管理の概要

Microsoft(R) Win32(R) アプリケーション プログラミング インターフェイス (API) では、 各プロセスは、 4Gバイトまでメモリをアドレス可能な32ビットの仮想アドレス空間をそれぞれ持っています。下位2Gバイト (0x00から0x7FFFFFFFまで) はユーザーが利用可能で、 上位2Gバイト (0x80000000から0xFFFFFFFFまで) はカーネル用に予約されています。プロセスが使用する仮想アドレスは、 メモリ内のオブジェクトの実際の物理番地を表していません。カーネルは、 各プロセスについて、 仮想アドレスを対応する物理アドレスに変換する「ページ マップ」というデータ構造を管理しています。

このトピックでは、 Win32のメモリ管理について説明しています。また、 メモリの割り当てと使用についても説明しています。

Win32のメモリ管理に関するトピックを次に示します。

仮想アドレス空間と物理記憶域

グローバル関数とローカル関数

標準C言語ライブラリ関数

仮想メモリ関数

ヒープ関数

共有メモリ

保護ページ

仮想メモリ関数の使用

メモリ管理関数

仮想アドレス空間と物理記憶域

各プロセスは、 すべてのプロセスから利用可能な総物理メモリ (RAM) 量にくらべて非常に大きい仮想アドレス空間を持っています。物理記憶域のサイズを増やすため、 カーネルは、 ディスクを予備記憶域として使います。実行中のすべてのプロセスが利用可能な記憶域の総計は、 物理メモリ (RAMと「ページング ファイル」用に利用可能なディスクの使用可能領域) の和です。「ページング ファイル」とは、 物理記憶域の量を増やすために使われるディスク ファイルです。物理記憶域と各プロセスの仮想 (論理) アドレス空間のメモリは、 「ページ」単位で構成されます。「ページ」のサイズは、 ホスト コンピュータによって異なります。たとえば、 x86コンピュータでは、 ホスト ページ サイズは4Kバイトです。

メモリ管理の柔軟性を最大限にするため、 カーネルは、 物理メモリのページをディスク上のページング ファイルとの間で移動します。カーネルは、 ページを物理メモリ内で移動するとき、 関連するプロセスのページ マップを更新します。物理メモリの領域が必要になると、 カーネルは、 物理メモリの最近使われていないページをページング ファイルに移動します。カーネルによる物理メモリの操作はアプリケーションから見ることはできず、 アプリケーションは自分の仮想メモリ空間しか操作できません。

プロセスの仮想アドレス空間のページは、 次に示す状態のいずれかになっています。

状態説明

未使用未使用ページは現在はアクセスできませんが、 コミットまたは予約すれば利用可能になります。

予約予約ページとは、 後で使うために取っておくプロセスの仮想アドレス空間のブロックです。プロセスは予約ページにはアクセスできず、 予約ページには物理記憶域はマップされていません。予約ページは、 ほかの割り当て操作 (mallocLocalAllocなど) で使えない仮想アドレスの範囲を予約します。アドレス空間の一部を予約し、 予約したページを後でコミットするには、 VirtualAlloc関数を使います。予約ページを解放するには、 VirtualFree関数を使います。

コミットコミット ページとは、 (メモリやディスクに) 物理記憶域が割り当てられているページです。コミット ページは、 アクセスなしや読み取り専用アクセスで保護したり、 読み書きアクセスを設定できます。コミット ページを割り当てるには、 VirtualAlloc関数を使います。GlobalAlloc関数とLocalAlloc関数は、 読み書きアクセスが可能なコミット ページを割り当てます。VirtualAllocで割り当てたコミット メモリは、 VirtualFree関数でコミット解除できます。VirtualFree関数は、 ページの記憶域を解放し、 ページの状態を予約に変更します。

グローバル関数とローカル関数

メモリを割り当てるには、 GlobalAlloc関数やLocalAlloc関数を使います。Win32 APIのリニアな32ビット環境では、 ローカル ヒープとグローバル ヒープの違いはありません。このため、 この2つの関数で割り当てたメモリ オブジェクトには違いはありません。

GlobalAllocLocalAllocは、 読み書き可能なプライベートのコミット ページにメモリ オブジェクトを割り当てます。「プライベート メモリ」は、 ほかのプロセスからはアクセスできません。GMEM_DDESHAREフラグを指定してGlobalAllocで割り当てたメモリは、 実際には、 Windowsバージョン3.xのようにはグローバルに共有されません。しかし、 このフラグは、 互換性を維持したり、 動的データ交換 (DDE) 操作の性能を向上させるために使われます。これ以外の理由で共有メモリを必要とする場合は、 ファイル マッピング オブジェクトを使ってください。複数のプロセスが同じ名前付き共有メモリを指定することによって、 同じファイル マッピング オブジェクトのビューをマップできます。ファイル マッピングについて、 詳しくはファイル マッピングの概要およびWin32(R) ベースのアプリケーションでのプロセス間通信の「共有メモリ」を参照してください。

サイズが32ビットで表現されるメモリ ブロックを割り当てるには、 GlobalAllocLocalAllocを使います。制限は、 ディスク上のページング ファイルの記憶域を含めた利用可能物理メモリの量だけです。Win32 APIでは、 グローバルやローカルのメモリ オブジェクトを操作するこのようなグローバルおよびローカルのメモリ関数は、 16ビット バージョンのWindowsとの互換性を保つために残されています。しかし、 16ビットのセグメント メモリ モデルから32ビットの仮想メモリ モデルに移行したため、 一部の関数やオプションは不要になったり、 無意味になっています。たとえば、 ローカル割り当て関数もグローバル割り当て関数も32ビットの仮想アドレスを返すため、 nearポインタやfarポインタはありません。

GlobalAllocLocalAllocは、 固定または移動可能のメモリ オブジェクトを割り当てることができます。移動可能オブジェクトは廃棄可能としてもマークできます。以前のバージョンのWindowsでは、 移動可能なメモリ オブジェクトはメモリ管理において重要な役割をはたしていました。システムは、 移動可能オブジェクトを利用して、 ほかのメモリ割り当てでメモリが必要になったときにヒープをコンパクト化することができました。仮想メモリを使うことによって、 システムは、 ページを使っているプロセスの仮想アドレスを変更せずに物理メモリのページを移動してメモリを管理できます。システムは、 物理メモリのページを移動するとき、 プロセスの仮想ページを物理ページの新しい位置にマップするだけです。しかし、 移動可能なメモリも、 廃棄可能なメモリの割り当てには役立ちます。物理記憶域がさらに必要になったとき、 システムは、 LRU (least recently used) アルゴリズムを使って、 ロックされていない廃棄可能メモリを解放します。頻繁には使わず、 再生成が容易なデータには、 廃棄可能メモリを使ってください。

GlobalAllocLocalAllocは、 固定メモリ オブジェクトを割り当てるとき、 呼び出し側がそのままメモリ アクセスに使える32ビット ポインタを返します。移動可能メモリ オブジェクトの場合、 呼び出し側のプロセスは、 GlobalLock関数やLocalLock関数を使います。これらの関数は、 メモリを移動したり廃棄できないようにロックします。各メモリ オブジェクトの内部データ構造にはロック カウントがあり、 カウントは最初は0になっています。移動可能メモリ オブジェクトの場合、 GlobalLockLocalLockがこのカウントを1増やし、 GlobalUnlock関数やLocalUnlock関数がカウントを1減らします。ロックされているメモリは移動したり廃棄することはできませんが、 GlobalReAlloc関数やLocalReAlloc関数でメモリ オブジェクトを再割り当てすることはできます。ロックされているメモリ オブジェクトのメモリ ブロックは、 ロック カウントが0になるまでロックされたままで、 ロック カウントが0になると移動したり廃棄できます。

GlobalAllocLocalAllocで割り当てたメモリの実際のサイズは、 要求したサイズよりも大きい場合があります。実際に割り当てられているバイト数を調べるには、 GlobalSize関数かLocalSize関数を使います。要求した量よりも割り当てられている量の方が大きいときは、 プロセスは割り当てられているメモリすべてを使うことができます。

GlobalReAlloc関数やLocalReAlloc関数は、 GlobalAllocLocalAllocで割り当てたメモリ オブジェクトのバイト単位のサイズや属性を変更します。サイズは、 増減どちらにも変更できます。

GlobalFree関数やLocalFree関数は、 GlobalAlloc LocalAlloc GlobalReAlloc LocalReAllocで割り当てたメモリを解放します。

グローバル メモリ関数とローカル メモリ関数には、 このほかに、 GlobalDiscard LocalDiscard GlobalFlags LocalFlags GlobalHandle LocalHandleがあります。ハンドルを無効化せずに特定の廃棄可能メモリ オブジェクトを廃棄するには、 GlobalDiscardLocalDiscardを使います。GlobalReAllocまたはLocalReAllocを使って同じハンドルに新しいメモリ ブロックを割り当てれば、 そのハンドルを再利用できます。特定のメモリ オブジェクトに関する情報を取得するには、 GlobalFlagsLocalFlagsを使います。この情報は、 オブジェクトのロック カウントや、 オブジェクトが廃棄可能かすでに廃棄されているかを示します。特定のポインタに対応するメモリ オブジェクトのハンドルを取得するには、 GlobalHandleLocalHandleを使います。

標準C言語ライブラリ関数

Win32のプロセスは、 標準C言語ライブラリ関数 (mallocfreeなど) を使ってメモリを安全に操作できます。以前のバージョンのWindowsではこれらの関数を使うと問題が発生する可能性がありましたが、 Win32 APIを使用するアプリケーションでは問題はありません。たとえば、 mallocは、 移動可能メモリを利用せずに固定ポインタを割り当てます。Win32では、 システムは仮想アドレスを変更せずに物理メモリのページを移動して自由にメモリを管理できるため、 問題は発生しません。同様に、 nearポインタとfarポインタの区別にも意味はなくなりました。このため、 廃棄可能メモリを割り当てるのでなければ、 標準C言語ライブラリ関数を使ってメモリを管理できます。

仮想メモリ関数

Win32 APIでは、 プロセスが自分の仮想アドレス空間内のページの状態を操作または判断するための仮想メモリ関数のセットを用意しています。 通常は、 標準割り当て関数 (GlobalAllocLocalAllocmallocなど) で十分です。しかし、 仮想メモリ関数によって、 標準割り当て関数にはない機能を利用できます。仮想メモリ関数が実行できる操作を次に示します。

プロセスの仮想アドレス空間の特定の範囲を予約し、 その範囲に関するほかの割り当て操作ができないようにします。ほかのプロセスの仮想アドレス空間には影響を与えません。ページを予約することによって、 物理記憶域を浪費せずに、 動的なデータ構造を拡張するためのアドレス空間の範囲を予約できます。予約したアドレス空間からは、 必要に応じて物理メモリを割り当てることができます。

プロセスの仮想アドレス空間の予約ページの一部をコミットして、 (RAMまたはディスクの) 物理記憶域を割り当て側のプロセスだけがアクセス可能にします。

特定の範囲のコミット ページに、 読み書きや読み取り専用、 アクセスなしなどの属性を設定します。標準割り当て関数では、 読み書きアクセスが可能なページしか割り当てることができません。

特定の範囲の予約ページを解放して、 仮想アドレスのその範囲を呼び出し側のプロセスのそれ以降の割り当て操作で利用可能にします。

コミット ページの特定の範囲をコミット解除して、 対応する物理記憶域を解放します。コミット ページをコミット解除すると、 そのページに使われていた記憶域をほかのプロセスの割り当てで利用できるようになります。

コミット メモリのページを物理メモリ (RAM) 内でロックし、 システムがそのページをページング ファイルにスワップ アウトできないようにします。

呼び出し側のプロセスや指定されたプロセスの仮想アドレス空間内の特定の範囲のページに関する情報を問い合わせます。

呼び出し側のプロセスや指定されたプロセスの仮想アドレス空間内の特定の範囲のページのアクセス保護を変更します。

仮想メモリ関数は、 メモリのページを操作します。仮想メモリ関数は、 指定されたサイズやアドレスを現在のコンピュータのページ サイズに丸めて使います。

現在のコンピュータのページ サイズを調べるには、 GetSystemInfo関数を使います。

VirtualAlloc関数は、 次の操作のいずれかを実行します。

未使用ページの予約

予約ページのコミット

未使用ページの予約とコミット

予約したりコミットするページの開始アドレスは、 プロセスが指定するか、 システムに決定させることができます。この関数は、 指定されたアドレスを適切なページ境界に丸めます。予約ページはアクセスできませんが、 PAGE_READWRITE PAGE_READONLY PAGE_NOACCESSのいずれかのフラグでコミット ページを割り当てることができます。ページをコミットするとページング ファイルに記憶域が割り当てられますが、 各ページは、 ページに対する最初の読み書きが発生したときに初めて初期化され、 物理メモリにロードされます。VirtualAlloc関数でコミットしたメモリをアクセスするには、 通常のポインタを使います。

VirtualFree関数は、 次の操作のいずれかを実行します。

コミット ページをコミット解除し、 ページを予約ページに変更します。ページをコミット解除すると、 ページに関連付けられている物理記憶域は解放され、 ほかのプロセスの割り当てで利用できるようになります。コミット ページの任意の部分をコミット解除できます。

予約ページのブロックを解放し、 ページの状態を未使用に変更します。ページのブロックを解放すると、 アドレスのその範囲をプロセスで割り当てられるようになります。予約ページを解放するには、 VirtualAllocで最初に予約したブロック全体を解放しなければなりません。

コミット ページのブロックを同時にコミット解除、 解放し、 ページの状態を未使用に変更します。ブロックには、 VirtualAllocで最初に予約したブロック全体を指定しなければならず、 全ページが現在コミットされていなければなりません。

プロセスは、 VirtualLock関数を使って、 コミット メモリのページを物理メモリ (RAM) 内にロックできます。ページをロックすると、 システムはそのページをページング ファイルにスワップ アウトしなくなります。この機能は、 重要なデータをディスクにアクセスせずにアクセスしなければならないときに役立ちます。メモリ内でページをロックするとシステムのメモリ管理機能が制限されるため、 この操作には危険な面があります。VirtualLockを過度に利用すると、 実行コードがページング ファイルにスワップ アウトされてしまうため、 システムの性能が低下する可能性があります。VirtualUnlock関数は、 VirtualLockでロックしたメモリをロック解除します。

VirtualQuery関数やVirtualQueryEx関数は、 プロセスのアドレス空間の特定のアドレスから始まる連続したページ領域に関する情報を返します。VirtualQueryは、 呼び出し側のプロセスのメモリに関する情報を返します。VirtualQueryExは、 デバッグ中のプロセスに関する情報を必要とするデバッガをサポートするための関数で、 指定されたプロセスのメモリに関する情報を返します。指定したアドレスは、 最も近いページ境界に丸めらて使われます。次に示す条件を満たす連続したページは、 すべて1つのページ領域として扱われます。

ページの状態 (コミット、 予約、 未使用) が同じであること。

最初のページが未使用でなければ、 VirtualAlloc関数を呼び出して予約したページから同時に割り当てたページ範囲にすべてのページが属していること。

すべてのページのアクセス保護 (PAGE_READONLYPAGE_READWRITE、 PAGE_NOACCESS) が同じであること。

プロセスは、 VirtualProtect関数を使って、 自分のアドレス空間の任意のコミット ページのアクセス保護を修正できます。たとえば、 読み書き可能ページを割り当てて重要なデータを格納し、 アクセスを読み取り専用またはアクセス禁止に変更して、 不注意による上書きからデータを保護できます。通常、 VirtualProtectは、 VirtualAllocで割り当てたページに使われますが、 ほかの割り当て関数でコミットしたページにも使えます。しかし、 VirtualProtectが全ページのアクセス保護を変更するのに対し、 ほかの関数が返すポインタはページ境界に整列しているとは限りません。VirtualProtectEx関数はVirtualProtectに似ていますが、 指定されたプロセスのメモリのアクセス保護を変更します。この関数は、 デバッグ中のプロセスのメモリにアクセスするデバッガで役立ちます。

ヒープ関数

プロセスは、 ヒープ関数を使って、 プライベート ヒープ (自分のアドレス空間内のページ ブロック) を作成することができます。プライベート ヒープを作成したプロセスは、 別の関数のセットを使って、 ヒープ内のメモリを管理できます。プライベート ヒープに割り当てたメモリと、 標準割り当て関数 (GlobalAllocLocalAlloc mallocなど) で割り当てたメモリには違いはありません。

HeapCreate関数は、 プライベート ヒープ オブジェクトを作成します。呼び出し側のプロセスは、 HeapAlloc関数を使って、 そのヒープにメモリ ブロックを割り当てることができます。HeapCreateには、 ヒープの初期サイズと最大サイズを指定します。初期サイズは、 ヒープに最初に割り当てられる読み書き可能なコミット ページの数を示します。最大サイズは、 予約ページの総数を示します。予約ページとコミット ページはプロセスの仮想アドレス空間内で連続しているため、 ヒープはそのまま拡張できます。HeapAllocによる要求が現在のコミット ページのサイズを超えたとき、 物理記憶域が利用可能ならば、 追加ページがこの予約領域から自動的にコミットされます。一度コミットしたページは、 プロセスが終了するか、 HeapDestroy関数を呼び出してヒープを破棄するまでコミット解除されません。

プライベート ヒープ オブジェクトのメモリは、 オブジェクトを作成したプロセスだけがアクセス可能です。DLLがプライベート ヒープを作成した場合、 そのヒープは、 DLLを起動したプロセスのアドレス空間に作成されます。このヒープは、 DLLを起動したプロセスだけがアクセス可能になります。

HeapAlloc関数は、 指定されたバイト数をプライベート ヒープから割り当てて、 割り当てたブロックを指すポインタを返します。このポインタ指すブロックは、 HeapFree関数で解放したり、 HeapSize関数でサイズを判断できます。

HeapAllocで割り当てたメモリは移動できません。システムはプライベート ヒープをコンパクト化できないため、 ヒープが断片化される可能性があります。

ヒープ関数は、 プロセスの起動時に、 プロセスに必要なメモリ量に対して十分な初期サイズを指定してプライベート ヒープを作成するときに使います。HeapCreate関数の呼び出しが失敗したら、 プロセスは、 終了するか、 メモリ不足をユーザーに通知してください。しかし、 関数が正常に終了したときは、 必要なメモリが存在することが保証されます。

共有メモリ

Win32 APIでは、 共有メモリはファイル マッピングで実現されています。ほかの割り当て方法 (GlobalAlloc関数、 LocalAlloc関数、 HeapAlloc関数、 VirtualAlloc関数) で割り当てたメモリは、 関数を呼び出したプロセスからしかアクセスできません。また、 DLLが割り当てたメモリは、 DLLを呼び出したプロセスのアドレス空間内にあり、 同じDLLを使っているほかのプロセスからはアクセスできません。

名前付きファイル マッピングによって、 共有メモリのブロックを簡単に作成できます。あるプロセスが名前を指定してCreateFileMapping関数でファイル マッピング オブジェクトを作成します。ほかのプロセスは、 同じ名前を指定して、 CreateFileMapping関数かOpenFileMapping関数を使ってマッピング オブジェクトのハンドルを取得します。イベント オブジェクトやセマフォ オブジェクト、 ミューテックス オブジェクト、 ファイル マッピング オブジェクトの名前は、 同じ名前空間を共有します。指定した名前が別の種類の既存のオブジェクトの名前に一致したときは、 エラーが発生します。名前付きオブジェクトを作成するときは、 一意な名前で作成してみてから、 関数の戻り値を調べて、 名前が重複していないか確認してください。

各プロセスは、 MapViewOfFile関数にファイル マッピング オブジェクトの自分自身のハンドルを指定して、 ファイルのビューを自分のアドレス空間にマップします。1つのファイル マッピング オブジェクトに対する各プロセスのビューは、 すべて、 物理記憶域の同じ共有可能ページにマップされます。しかし、 MapViewOfFileEx関数を使ってビューを特定のアドレスにマップしなければ、 マップされている各ビューの仮想アドレスはプロセスによって異なります。マップされているファイル ビューに使われている物理記憶域のページは共有可能ですが、 グローバルではありません。ファイルのビューをマップしていないプロセスからはこのページはアクセスできません。

ファイル マッピング オブジェクトは、 マップされているビューを物理メモリからディスクにマップするときにシステムが使用するディスク ファイルに関連付けられています。このディスク スワップ ファイルにはシステムのページング ファイルが使われますが、 ファイル マッピング オブジェクトを作成するときにほかのファイルを指定することもできます。ほかのファイルを指定した場合、 メモリはファイルの内容で初期化されます。ファイル システム内の特定のファイルのマッピングは、 既存のファイルのデータを共有しなければならないときや、 共有に関係するプロセスが生成したデータを保管するときに役立ちます。特定のファイルをマップするときは、 排他アクセスでファイルをオープンし、 共有メモリを使い終わるまでハンドルをオープンしたままにしてください。これによって、 ほかのプロセスがファイルを別のハンドルでオープンしてReadFileWriteFileを使ったり、 同じファイルからマッピング オブジェクトをさらに作成することができなくなります (このような操作が行われると、 予期しない事態が発生する可能性があります)。

ファイルのビューをマップしてコミットしたページは、 マッピング オブジェクトのビューを持つ最後のプロセスが終了するか、 UnmapViewOfFile関数を呼び出してビューをマップ解除すると解放されます。このとき、 マッピング オブジェクトに特定のファイルが関連付けられていれば、 そのファイルは更新されます。また、 FlushViewOfFile関数を呼び出して、 関連付けられているファイルを強制的に更新することもできます。

ファイル マッピングについて、 詳しくはファイル マッピングの概要を、 DLLでの共有メモリの例については、 ダイナミック リンク ライブラリの概要を参照してください。

複数のプロセスが共有プロセスに対する読み書きアクセスを持つときは、 メモリへのアクセスを同期してください。プロセス間での同期については同期の概要を参照してください。

アクセスの妥当性検査

Win32 APIは、 特定のメモリ アドレスやアドレス範囲に対して特定の種類のアクセスが可能かどうかを調べるための関数を用意しています。アクセス妥当性検査関数を次に示します。

関数説明

IsBadCodePtr呼び出し側のプロセスが指定されたアドレスのメモリに読み取りアクセスを行えるか調べます。

IsBadReadPtr呼び出し側のプロセスが指定されたアドレス範囲のメモリに読み取りアクセスを行えるか調べます。

IsBadStringPtr NULLで終わる文字列を指すポインタが指すメモリに呼び出し側のプロセスが読み取りアクセスを行えるか調べます。この関数は、 指定された文字数を調べ終わるか、 またはNULL文字を見つけるまで、 アクセス可能か調べます。

IsBadWritePtr指定されたアドレス範囲のメモリに呼び出し側のプロセスが書き込みアクセスを行えるか調べます。

また、 通常のメモリ割り当てと複数のセグメントを使う大規模メモリ割り当てを区別する以前のバージョンのWindowsとの互換性を保つため、 IsBadHugeReadPtr関数やIsBadHugeWritePtr関数も利用可能になっています。Win32 APIでは、 この2つの関数は、 IsBadReadPtrIsBadWritePtrと同じです。

プリエンプティブなマルチタスク環境では、 調査中のメモリに対するプロセスのアクセス権をほかのスレッドが変更する可能性があります。上記の関数がメモリへのアクセスが可能なことを示していても、 メモリにアクセスするときは、 構造化例外処理を使ってください。構造化例外処理を利用すると、 システムは、 アクセス保護例外が発生したときはプロセスに通知し、 プロセスが例外を処理できるようにします。構造化例外処理について、 詳しくは構造化例外処理の概要を参照してください。

保護ページ

アプリケーションでは、 メモリ ページのPAGE_GUARDページ保護修飾子フラグを設定して「保護ページ」を確立します。このフラグは、 VirtualAllocVirtualProtect、 およびVirtualProtectEx関数にほかのページ保護フラグと一緒に指定できます。PAGE_GUARDフラグは、 NO_ACCESS以外のページ保護フラグとともに使用できます。

保護ページ内のアドレスにプログラムがアクセスしようとすると、 オペレーティング システムからSTATUS_GUARD_PAGE (0x80000001) 例外があげられます。オペレーティング システムはPAGE_GUARDフラグをクリアしてそのメモリ ページの保護ページ状態を解除します。STATUS_GUARD_PAGE例外でそのメモリ ページに対する次回のアクセスが停止されることはありません。

システム サービス時に保護ページ例外が起こった場合、 そのサービスは失敗し、 通常なんらかの失敗状態を示すインジケータが返されます。関連したメモリ ページの保護ページ状態はシステムにより解除されるため、 もう一度同じシステム サービスを起動してもSTATUS_GUARD_PAGE例外が原因で失敗することはありません (もちろんだれかが保護ページを再確立していない場合にかぎります)。

このように、 保護ページはメモリ ページのアクセスに対する1回限りの警告を提供します。大規模な動的データ構造の成長を監視する必要のあるアプリケーションにこれを役立てることができます。たとえば、 オペレーティング システムの中には保護ページを使ってスタックの自動チェックを実現しているものもあります。

次にあげたプログラムは、 1回限りの保護ページ動作と、 その結果どのようにしてシステム サービスが失敗するかを示したものです。

#include <windows.h>

#include <stdio.h>

#include <stdlib.h>

int main()

{

// local variables

LPVOID lpvAddr;

DWORD cbSize;

BOOL vLock;

LPVOID commit;

// amount of memory we'll allocate

cbSize = 512;

// try to allocate some memory

lpvAddr = VirtualAlloc(NULL,cbSize,MEM_RESERVE,PAGE_NOACCESS);

// if we failed ...

if(lpvAddr == NULL)

fprintf(stdout,"VirtualAlloc failed on RESERVE with %ld\n",

GetLastError());

// try to commit the allocated memory

commit = VirtualAlloc(NULL,cbSize,MEM_COMMIT,PAGE_READONLY|PAGE_GUARD);

// if we failed ...

if(commit == NULL)

fprintf(stderr,"VirtualAlloc failed on COMMIT with %ld\n",

GetLastError());

else// we succeeded

fprintf(stderr,"Committed %lu bytes at address %lp\n",

cbSize,commit);

// try to lock the committed memory

vLock = VirtualLock(commit,cbSize);

// if we failed ...

if(!vLock)

fprintf(stderr,"Cannot lock at %lp, error = %lu\n",

commit,GetLastError());

else// we succeeded

fprintf(stderr,"Lock Achieved at %lp\n",commit);

// try to lock the committed memory again

vLock = VirtualLock(commit,cbSize);

// if we failed ...

if(!vLock)

fprintf(stderr,"Cannot get 2nd lock at %lp, error = %lu\n",

commit,GetLastError());

else// we succeeded

fprintf(stderr,"2nd Lock Achieved at %lp\n",commit);

} // endof function

このプログラムの出力は次のようになります。

Committed 512 bytes at address 003F0000

Cannot lock at 003F0000, error = 0x80000001

2nd Lock Achieved at 003F0000

1回目のメモリ ブロックのロックが失敗し、 STATUS_GUARD_PAGE例外があがっていることに注意してください。メモリ ブロックの保護ページは1回目のロックでオフに切り替わっているため、 2回目のロックは成功します。

仮想メモリ関数の使用

ここでは、 仮想メモリ関数による動的割り当てを説明します。

次の例は、 VirtualAlloc関数とVirtualFree関数を使って動的な配列に必要なメモリを予約して割り当てる方法を示しています。まず、 VirtualAllocを呼び出してページのブロックを割り当てます。このとき、 ベース アドレス パラメータにNULLを指定して、 ブロックの位置をシステムに決定させます。その後、 予約した領域からページをコミットしなければならなくなると、 コミットするページのベース アドレスを指定して、 VirtualAllocを呼び出します。

この例では、 予約領域からページをコミットするときに、 tryexceptによる構造化例外処理構文を使っています。tryブロックの実行中にページ保護例外が発生すると、その前のexceptブロックの式のフィルタ関数が実行されます。フィルタ関数でページの割り当てが成功したときは、 tryブロックの例外が発生した位置から実行が再開されます。そうでないときは、 exceptブロックの例外ハンドラが実行されます。構造化例外処理について、 詳しくは構造化例外処理の概要を参照してください。

このような動的割り当てを行わずに、 領域全体を予約せずにコミットすることもできます。しかし、 この方法では、 不要なページのための物理記憶域が浪費されるため、 ほかのプロセスがその分の記憶域を利用できなくなります。

この例は、 VirtualFreeを使って、 使い終わった予約ページやコミット ページを解放します。この関数は、 2回呼び出されます。最初はコミット ページをコミット解除するために呼び出し、 2回目は予約ページ全体を解放するために呼び出します。

#define PAGELIMIT 80
#define PAGESIZE 0x1000

INT PageFaultExceptionFilter(DWORD);
VOID MyErrorExit(LPTSTR);

LPTSTR lpNxtPage;
DWORD dwPages = 0;

VOID UseDynamicVirtualAlloc(VOID) {
LPVOID lpvBase;
LPTSTR lpPtr;
BOOL bSuccess;
DWORD i;

/* Reserve pages in the process's virtual address space. */

lpvBase = VirtualAlloc(
NULL,/* system selects address*/
PAGELIMIT*PAGESIZE, /* size of allocation*/
MEM_RESERVE,/* allocates reserved pages */
PAGE_NOACCESS);/* protection = no access*/
if (lpvBase == NULL )
MyErrorExit("VirtualAlloc reserve");

lpPtr = lpNxtPage = (LPTSTR) lpvBase;

/*
* Use try-except structured exception handling when
* accessing the pages. If a page fault occurs, the
* exception filter is executed to commit another page
* from the reserved block of pages.
*/

for (i=0; i < PAGELIMIT*PAGESIZE; i++) {

try {

/* Write to memory. */

lpPtr[i] = 'a';
}

/*
* If there is a page fault, commit another page
* and try again.
*/

except ( PageFaultExceptionFilter(
GetExceptionCode() ) ) {

/*
* This is executed only if the filter function is
* unsuccessful in committing the next page.
*/

ExitProcess( GetLastError() );

}

}

/* Release the block of pages when you are finished using them. */

/* First, decommit the committed pages. */

bSuccess = VirtualFree(
lpvBase,/* base address of block*/
dwPages*PAGESIZE, /* bytes of committed pages */
MEM_DECOMMIT);/* decommit the pages*/

/* Release the entire block. */

if (bSuccess)
bSuccess = VirtualFree(
lpvBase,/* base address of block*/
0,/* releases the entire block */
MEM_RELEASE);/* releases the pages*/

}

INT PageFaultExceptionFilter(DWORD dwCode) {
LPVOID lpvResult;

/* If the exception is not a page fault, exit. */

if (dwCode != EXCEPTION_ACCESS_VIOLATION) {
printf("exception code = %d\n", dwCode);
return EXCEPTION_EXECUTE_HANDLER;
}

printf("page fault\n");

/* If the reserved pages are used up, exit. */

if (dwPages >= PAGELIMIT) {
printf("out of pages\n");
return EXCEPTION_EXECUTE_HANDLER;
}

/* Otherwise, commit another page. */

lpvResult = VirtualAlloc(
(LPVOID) lpNxtPage, /* next page to commit*/
PAGESIZE,/* page size, in bytes*/
MEM_COMMIT,/* alloc committed page */
PAGE_READWRITE);/* read-write access*/
if (lpvResult == NULL ) {
printf("VirtualAlloc failed\n");
return EXCEPTION_EXECUTE_HANDLER;
}

/*
* Increment the page count, and advance lpNxtPage
* to the next page.
*/

dwPages++;
lpNxtPage += PAGESIZE;

/* Continue execution where the page fault occurred. */

return EXCEPTION_CONTINUE_EXECUTION;

}

メモリ管理関数

メモリ管理に関するWin32関数を次に示します。

CopyMemory

FillMemory

GetProcessHeap

GetProcessHeaps

GlobalAlloc

GlobalDiscard

GlobalFlags

GlobalFree

GlobalHandle

GlobalLock

GlobalMemoryStatus

GlobalReAlloc

GlobalSize

GlobalUnlock

HeapAlloc

HeapCompact

HeapCreate

HeapDestroy

HeapFree

HeapLock

HeapReAlloc

HeapSize

HeapUnlock

HeapValidate

IsBadCodePtr

IsBadHugeReadPtr

IsBadHugeWritePtr

IsBadReadPtr

IsBadStringPtr

IsBadWritePtr

LocalAlloc

LocalDiscard

LocalFlags

LocalFree

LocalHandle

LocalLock

LocalReAlloc

LocalSize

LocalUnlock

MoveMemory

VirtualAlloc

VirtualFree

VirtualLock

VirtualProtect

VirtualProtectEx

VirtualQuery

VirtualQueryEx

VirtualUnlock

ZeroMemory

使われなくなったか、 または削除された関数

ChangeSelector

DefineHandleTable

FreeSelector

GetCodeInfo

GetFreeSpace

GlobalCompact

GlobalDosAlloc

GlobalDosFree

GlobalFix

GlobalLRUNewest

GlobalLRUOldest

GlobalNotify

GlobalPageLock

GlobalPageUnlock

GlobalUnfix

GlobalUnWire

GlobalWire

LimitEmsPages

LocalCompact

LocalInit

LocalShrink

LockSegment

SetSwapAreaSize

SwitchStackBack

SwitchStackTo

UnlockSegment

▲ページトップに戻る

【本を読んでもよくらからない】 … 個別指導でわかりやすくお教えします