メモリ
ハンドルって何JARO?(JAROに聞いたら(以下略
Macでプログラミングといったら、どうしても避けて通れないMemory Managerのお話……。めんどくさくてひどく疲れるのに、それだけで何か作れるというわけでもないので後回しにしてきましたが、そろそろ潮時かということで。あー、でもOS Xではもはやどうでもよくなった話題です。OSの進化ってすごい……!
Macのシステムは、当初シングルタスク(同時に1つのアプリケーションしか動かせない)だったのを、「増築」してマルチタスク(同時に複数のアプリケーションを動かせる)に対応させました。それはそれですごいですが、メモリの扱いがシングルタスク時代を引きずってしまいました。
1. 各アプリケーションはコンピュータの空きメモリの全部を使えない
2. 各アプリケーションはそれぞれ割り当てられたメモリの範囲内でやりくりする
といった具合。メモリの割り当ては「情報を見る」→「メモリ」で設定するあれです。
ということは、たとえば割り当てメモリが1MBだとすると、アプリケーションはメモリ1MBのコンピュータ上で動作しているのと同じような状況になります。
「そのアプリがメモリをそれだけしか使わないなら特に問題ないんでは?」と思われるかもしれませんが、メモリのサイズが小さいと断片化(フラグメンテーション)の問題が深刻なのです……。
↓断片化の説明
__________ 1. 最初に空きメモリ1MB
□□□_______ 2. メモリ0.3MBを使う
□□□■■■____ 3. メモリ0.3MBを使う
□□□■■■△△__ 4. メモリ0.2MBを使う
□□□___△△__ 5. 真ん中のメモリ0.3MBを解放
6. メモリ0.4MBが使えない(空きメモリは0.5MBあるのに!)
ここで、
□□□△△_____ 6. メモリ0.2MBを移動
とできれば、
□□□△△▲▲▲▲_ 7. メモリ0.4MBを使う
と解決です。でも、ポインタを使ったプログラムで勝手にメモリブロックの移動はできませんね。
そこで! リロケータブルブロックとハンドルが登場ー!
リロケータブルブロックは移動可能なメモリブロックで、ハンドルはその参照です。ハンドルの正体は……システムが管理するマスタポインタ、つまり「リロケータブルブロックへのポインタ」へのポインタ(二重のポインタ)です。メモリブロックの移動と同時にマスタポインタの値を書き換えるので、ハンドルの参照性が保たれます。
この「メモリの整理」が実行されるのは、システム関数の呼び出しなどで制御がシステム側にある時間だけです。
では、Memory Managerの関数の紹介に入っていきます!
Ptr NewPtr(Size byteCount)
Ptr NewPtrClear(Size byteCount)
void DisposePtr(Ptr p)
それぞれ標準C言語のmalloc、calloc、freeの替わりになる関数です。起動後すぐにメモリを確保して最後まで使う、という場合には断片化は心配しなくていいのでこれを使いましょう。(普通にmallocとかも使えるんですが……むしろその方がいいかも?)
void BlockMove(const void *srcPtr, void *destPtr, Size byteCount)
これはmemmoveの替わり……。BlockMoveDataというのもありますが、調べても違いがよく分かりませんでした。
使用例:
/* int型配列(長さ100) */ list = (int *)NewPtr(100 * sizeof(int)); if (list != NULL) { ...
Handle NewHandle(Size byteCount)
Handle NewHandleClear(Size byteCount)
void DisposeHandle(Handle h)
NewHandleはリロケータブルブロックを作って、そのハンドル(メモリ確保失敗の時はNULL)を返します。
新しく作った時点では、ハンドルは「ロック無し、パージ不可」に設定されています。ん、ロックやパージって何だ……。
ロックというのは、ハンドルがロックありに設定されていると、そのブロックは移動の対象にならないというものです。マスタポインタの値が変わらないことが前提の処理をする前には、ハンドルのロックが必要です。(ここはバグの温床
パージの方は、辞書的には「粛正」とか「浄化」という意味(歴史の授業でレッドパージとかあったね……)になりますが、ここでは「リロケータブルブロックのメモリを解放、マスタポインタの値はNULLを代入」ということを指します。正直言って、どういう時にパージ可の設定を使ったらいいのか不明……。ちなみに、ロックありの時はパージ可でもパージされることはないです。
使用例:
/* MyData構造体 */ data = (struct MyData**)NewHandle(sizeof(struct MyData)); if (data != NULL) { ...
void HLock(Handle h)
void HUnlock(Handle h)
void HPurge(Handle h)
void HNoPurge(Handle h)
HLock/HUnlockがそれぞれハンドルをロックあり/無しに設定する関数です。HPurge/HNoPurgeはそれぞれハンドルをパージ可/不可に設定します。
void MoveHHi(Handle h)
void HLockHi(Handle h)
ハンドルのロックは断片化の原因になるので、ブロックをメモリの高い方(?)へと移動させてからするといいらしいです。
そのための関数がMoveHHiとHLockHiで、MoveHHiの方は移動だけ、HLockHiではロックする効果が加わります。(MoveHHiの使い道って……?
HLockHiはHLockより移動の分だけ処理が重くなります。
使用例:
/* ハンドルロック */ HLock((Handle)h); ... /* ロック解除 */ HUnlock((Handle)h);
SInt8 HGetState(Handle h)
void HSetState(Handle h, SInt8 flags)
ハンドルの属性(ロックあり/無し、パージ可/不可)を保存・復元するための関数です。
HLockなどはロック回数をカウントしているわけではなく、ロックの有無が分からないハンドル(関数の仮引き数など)をHLock(HLockHi)/HUnlockで挟んで処理すると危険! 元々ロックされていたのをロック無しにしてしまいます。
あとは使用例を見てもらえば分かると思います。(手抜き
使用例:
SInt8 hState; ... /* ハンドルの属性を保存 */ hState = HGetState((Handle)h); /* ハンドルロック */ HLockHi((Handle)h); ... /* ハンドルの属性を復元 */ HSetState((Handle)h, hState);
void MoreMasterPointers(UInt32 inCount)
追加のマスタポインタ用ブロックを確保する関数です。引き数はマスタポインタの個数。
マスタポインタはあらかじめ64個分の領域が確保されていて、これを使い切っても追加のマスタポインタ用ブロックが自動で確保されます。が、このブロックはリロケータブルブロックではないので、初期化時に手動で確保しておく方がいいです。
使用例:
/* 64個追加ー */ MoreMasterPointers(64); /* 何個いるかなんて分からん! */
他にもまだありますが、あまり使いそうにないので割愛ー。
ハンドルは、(*h)->aみたいな書き方をしないといけないし、ロック忘れで実行時に誤作動などもあって難しいです。新しい時代のOSで廃れていくのもむべなるかなと思うぎるばーとなのでした。了。