Allocator
以下は、Mac OS X 10.4.11 上の Xcode 2.5 で説明しています。
1 実行
まず、Xcode 上でビルドして実行してみます。Xcode の実行ログは以下のようになります。
Counting Allocator Test
At start, number of allocations: 0
After creation, number of allocations: 5
After mutations, number of allocations: 5
After releasing, number of allocations: 0
Reduced Malloc Allocator Test
At start, number of addt'l allocations: 0
After creation, number of addt'l allocations: 2
After mutations, number of addt'l allocations: 2
After releasing, number of addt'l allocations: 0
本当は、各行の後に空行がありますが、ここでは省いています。英語がニガテな人にはわかりにくいかもしれません。これを訳してみます。
Allocator のカウントテスト
開始時、割り当ての数: 0
作成後、割り当ての数: 5
変更後、割り当ての数: 5
解放後、割り当ての数: 0
削減 Malloc Allocator テスト
開始時、追加割り当ての数: 0
作成後、追加割り当ての数: 2
変更後、追加割り当ての数: 2
解放後、追加割り当ての数: 0
これだけ見ても、何のことやら、という感じです。ともかくも、ソースを見てみましょう。
2 ファイル構成
プロジェクトを構成しているファイルは、AllocatorExample.c だけです。あとは External Frameworks and Libraries 内で Core Foundation フレームワークに対してリンクされていることに注意してください。
3 AllocatorExample.c
3.1 main 関数
まず、main 関数を見てみます。AllocatorExample.c では以下のようになっています。
AllocatorExample.c > main
int main (int argc, const char *argv[]) {
countingAllocatorExample();
reducedMallocAllocatorExample();
return 0;
}
コマンド行からの引数は一切利用せず、引数なしの 2 つのメソッドを呼び出しているだけです。これらの関数を見ていきましょう。
3.2 show 関数
その前に、サンプルでログ出力をするため、共通に定義されている関数があります。それは show です。この関数は、CFString オブジェクトである書式文字列と、可変個の書式変数の値を渡すことで、結果を printf で出力してくれます。
AllocatorExample.c > show
void show(CFStringRef formatString, ...) {
CFStringRef resultString;
CFDataRef data;
va_list argList;
va_start(argList, formatString);
// 可変個引数受けとり用リスト作成
resultString
= CFStringCreateWithFormatAndArguments(NULL, NULL, formatString, argList);
// リストを使って CFString を作成
va_end(argList);
// 可変個引数受けとり用リスト解放
data = CFStringCreateExternalRepresentation
(NULL, resultString, CFStringGetSystemEncoding(), '?');
// 出力用のシステムエンコーディング文字データ作成
if (data != NULL) {
printf ("%.*s\n\n",
(int)CFDataGetLength(data), CFDataGetBytePtr(data));
// 標準出力
CFRelease(data);
// データ解放
}
CFRelease(resultString);
// 変換後文字列を解放
}
まず、可変個引数の受けとり用のリストを作成します。ここでは行っていませんが、このリストから type va_arg(va_list ap, type); で引数をとりだすことができます。type とあるように、あらかじめ型がわかっている必要があります。このリストを使って、CFStringCreateWithFormatAndArguments 関数で、resultString に結果の文字列を作成しています。この関数の引数は、最初がアロケーター(NULL はデフォルト)、次は書式指定のオプション(現在は無効なのでつねに NULL)、その次が書式文字列の CFString で、最後が va_list 型になっています。アロケーターがデフォルトになっていることに注意してください。これにより、この割り当ては、サンプルのアロケーターを経由しません。
次に、CFStringCreateExternalRepresentation で printf に渡すための文字列データが入れられた CFData を作っています。最後の '?' は変換できない文字をどういう文字として表すかを指定するものです。このデータ作成が成功したら、次に printf に渡します。「%.*s\n\n」という書式文字列で、%s はヌル終端の文字列を渡すことを指定し、その前の精度指定「.*」は、最大文字数を表し、ここでは * で引数として渡すことを指定しています。そのため、文字列後の引数は、データのバイト長、データという順番で渡されています。あとは割り当てたデータを解放しています。
この関数に渡す書式文字列は、たいてい CFSTR マクロでコード中に直接書かれます。このマクロは、定数文字列から CFString を作ります。ただし、7 ビット ASCII(127 までのコード)しかサポートしてないことに注意してください。日本語を使うなら、別の方法が必要です。
3.3 countingAllocatorExample 関数
countingAllocatorExample は、以下のようになっています。
AllocatorExample.c > countingAllocatorExample
void countingAllocatorExample(void) {
CFStringRef str1, str2, str3;
// ローカル変数宣言
CFMutableStringRef mStr;
CFAllocatorRef countingAllocator;
// 使用するアロケーター用変数
show(CFSTR("Counting Allocator Test"));
// タイトル文字列を表示
countingAllocator = CreateCountingAllocator(NULL);
// 1 使用するアロケーター作成
show(CFSTR("At start, number of allocations: %d"),
NumOutstandingAllocations(countingAllocator));
// 2 割り当て数表示
str1 = CFStringCreateWithCString(countingAllocator,
"Hello World", kCFStringEncodingASCII);
// C 言語文字列作成
str2 = CFStringCreateWithPascalString(countingAllocator,
"\pHello World", kCFStringEncodingASCII);
// Pascal 言語文字列作成
mStr = CFStringCreateMutableCopy(countingAllocator, 0, str1);
// 変更可能コピー作成
str3 = CFStringCreateCopy(countingAllocator, mStr);
// コピー作成
show(CFSTR("After creation, number of allocations: %d"),
NumOutstandingAllocations(countingAllocator));
// 割り当て数表示
CFStringAppend(mStr, str1);
// 文字列連結
CFStringAppend(mStr, str1);
// 文字列連結
CFStringAppend(mStr, str1);
// 文字列連結
show(CFSTR("After mutations, number of allocations: %d"),
NumOutstandingAllocations(countingAllocator));
// 割り当て数表示
CFRelease(str1);
// 文字列解放
CFRelease(str2);
// 文字列解放
CFRelease(mStr);
// 文字列解放
CFRelease(str3);
// 文字列解放
show(CFSTR("After releasing, number of allocations: %d"),
NumOutstandingAllocations(countingAllocator));
// 割り当て数表示
CFRelease(countingAllocator);
// アロケーター解放
}
まず、show を使って文字列を表示しています。タイトル表示の後は、1 で使用するアロケーターを作成しています。この関数と、それにともなうコールバック関数等は次節で説明します。
次に、2 で、最初の割り当て数表示が行われています。このとき、割り当て数を調べるため、NumOutstandingAllocations という関数が呼ばれていますが、これも次節で説明します。
その後は文字列を 4 回作成やコピーしています。最初の起動時に 5 という数が示されていたことに注意してください。そして、変更可能文字列に別の文字列をつなげています。その後、再び割り当て数を表示し、文字列を 4 回解放し、再び割り当て数を表示しています。最後に、不要になったアロケーターを解放しています。
3.4 countingAllocator について
前半に使われるアロケーターである、countingAllocator は、CreateCountingAllocator(NULL) で作られていました。この関数を見てみます。
AllocatorExample.c > CreateCountingAllocator
static CFAllocatorRef CreateCountingAllocator(CFAllocatorRef alloc) {
CFAllocatorContext context
= {0, NULL, NULL, (void *)free, NULL,
countingAlloc, countingRealloc, countingDealloc, NULL};
context.info = malloc(sizeof(int));
// info フィールドは、割り当て・割り当て解除の数を保つ int を指している
// context の 3 番目のスロットは、アロケータが解放されたときに、
// info フィールドも正しく解放されることを確実にしている
*(int *)(context.info) = 0;
// アロケーターを作成して返す
return CFAllocatorCreate(alloc, &context);
}
C 言語にくわしくなく、気になる人がいるかもしれないので、いちおう説明しておきますが、static 関数は適用範囲をこのソースファイル内だけに限定するという宣言です。このサンプルの場合、単一ソースでヘッダもないので、あまり気にしないで大丈夫です。まず最初に CFAllocatorContext 構造体を設定しています。これは以下のように定義されています。
CFBase.h > CFAllocatorContext
typedef struct {
CFIndex version;
// バージョン(現在は 0 のみ)
void * info;
// 独自の任意データを指す
CFAllocatorRetainCallBack retain;
// info の保持コールバック
CFAllocatorReleaseCallBack release;
// info の解放コールバック
CFAllocatorCopyDescriptionCallBack copyDescription;
// info の記述用コールバック
CFAllocatorAllocateCallBack allocate;
// 割り当て用コールバック
CFAllocatorReallocateCallBack reallocate;
// 再割り当て用コールバック
CFAllocatorDeallocateCallBack deallocate;
// 割り当て解除用コールバック
CFAllocatorPreferredSizeCallBack preferredSize;
// サイズ判定用コールバック
} CFAllocatorContext;
この構造体の最初は現在はつねに 0 です。次は、このオブジェクトで自由に利用するデータのためのポインタです。後で設定するため、最初は NULL になっています。その次の 3 つのコールバックは、この info に対するコールバックです。これらのコールバックは、アロケーター自体の機能とは関係ありません。ここでは、解放だけ設定されています。その後の 3 つは、このソース内で定義されている関数が設定されています。最後のサイズ判定は、このアロケーターでは設定していません。
次に、info を int 型として malloc を使って割り当てて代入しています。解放はさっきの構造体に設定された関数が呼ばれるため、心配することはありません。この info は、独自に定義した構造体など、好きなものが使えます。それから、最初の値として 0 を設定しています。最後に引数として渡されたアロケーター(NULL なのでシステムデフォルト)を使って、アロケーター自体を割り当てて作成しています。
これでアロケーターが作成できました。このアロケーターを使って、割り当てなどを行うと、その時、構造体に設定してあったコールバック関数が呼ばれることになります。まず、割り当ての場合を見てみましょう。
AllocatorExample.c > countingAlloc
static void *countingAlloc(CFIndex size, CFOptionFlags hint, void *info) {
(*(int *)info)++; // カウントを増分
return countingRealloc(NULL, size, hint, info);
}
行っていることは簡単です。まずカウントを増分して、それから再割り当て用のコールバックを呼んでいるだけです。引数もそのまま渡しているので、再割り当て用のコールバックで説明します。
AllocatorExample.c > countingRealloc
static void *countingRealloc(void *oPtr, CFIndex size, CFOptionFlags hint, void *info) {
int *ptr = oPtr;
// int 境界にあうようにサイズを切り上げる
size = ((size + sizeof(int) - 1) / sizeof(int)) * sizeof(int);
if (ptr) {
ptr = ptr - 2; // 元々割り当てた最初に戻る
verifyMemory(ptr);
ptr = realloc(ptr, size + sizeof(int) * 6);
} else {
ptr = malloc(size + sizeof(int) * 6);
}
ptr[0] = size; // ガードは含めない
ptr[1] = 0x42424242;
ptr[2 + size / sizeof(int)] = 0x42424242;
ptr[2 + size / sizeof(int) + 1] = 0x42424242;
ptr[2 + size / sizeof(int) + 2] = 0x42424242;
ptr[2 + size / sizeof(int) + 3] = 0x42424242;
return (void *)(ptr + 2);
}
まず、最初に int 境界にあわせてサイズを調整しています。それから、ポインタが渡されたかどうかを調べます。割り当てルーチンからはポインタが渡されないので、その場合は単に malloc で割り当てます。ポインタが渡されたら、ポインタを 2 つ下げて、元々割り当てたものに戻しています。これは後のほうを見るとわかりますが、このアロケーターで割り当てたブロックには、最初にブロックのサイズ、次に 0x42424242 が確認のためのガードとして使われています。また、最後の 4 つのスロットにも 0x42424242 が入れられています。これは、このアロケーターで割り当てられたかどうかを調べるため、次の verifyMemory 関数で使われています。
AllocatorExample.c > verifyMemory
void verifyMemory(int *ptr) {
int size = ptr[0];
if (ptr[1] != 0x42424242) abort();
if (ptr[2 + size / sizeof(int)] != 0x42424242) abort();
if (ptr[2 + size / sizeof(int) + 1] != 0x42424242) abort();
if (ptr[2 + size / sizeof(int) + 2] != 0x42424242) abort();
if (ptr[2 + size / sizeof(int) + 3] != 0x42424242) abort();
}
このように、逆のチェックを行うことで、このアロケーターで割り当てられたメモリかをチェックし、違うなら abort しています。
元の割り当てコールバックに戻って説明を続けます。チェックした後は、渡されたポインタをサイズを使って realloc で再割り当てします。そして、上で説明したように、ガードをメモリブロックに入れます。最後は、ポインタを 2 つ進めて、ガードの部分を受けとった側は意識しないようにしています。
つぎに、解放コールバックを見てみます。
AllocatorExample.c > countingDealloc
static void countingDealloc(void *ptr, void *info) {
(*(int *)info)--; // カウントを減分する
ptr = (int *)ptr - 2; // ポインタを実際のブロックに戻す
verifyMemory(ptr);
free(ptr);
}
最初にカウントを減らして、次に上で説明したように、ポインタを malloc などで返されたものに戻します。それから、このアロケーターで割り当てたものかチェックして、最後にそれを解放しています。
これで、アロケーターが何をしているかがわかったので、NumOutstandingAllocations 関数も理解できると思います。
AllocatorExample.c > NumOutstandingAllocations
static CFIndex NumOutstandingAllocations(CFAllocatorRef alloc) {
CFAllocatorContext context;
// 情報受けとり用のローカル変数
context.version = 0;
// バージョンを 0 にする
CFAllocatorGetContext(alloc, &context);
// コンテキストを取得
return (*(int *)(context.info));
// info フィールドの値を返す
}
アロケーターが理解できたので、コメントだけでわかると思います。
サンプルを起動させると、割り当て後の数として 5 が表示されていました。文字列自体は 4 つしか作っていないのに、この数はどこで増えているのでしょうか。せっかくサンプルなので、countingAllocatorExample 関数内に追加コードを加えてビルドして実行してみます。
AllocatorExample.c > NumOutstandingAllocations
...
str1 = CFStringCreateWithCString(countingAllocator,
"Hello World", kCFStringEncodingASCII);
show(CFSTR("after 1st string: %d"),
NumOutstandingAllocations(countingAllocator));
// 追加
str2 = CFStringCreateWithPascalString(countingAllocator,
"\pHello World", kCFStringEncodingASCII);
show(CFSTR("after 2nd string: %d"),
NumOutstandingAllocations(countingAllocator));
// 追加
mStr = CFStringCreateMutableCopy(countingAllocator, 0, str1);
show(CFSTR("after 3rd string: %d"),
NumOutstandingAllocations(countingAllocator));
// 追加
str3 = CFStringCreateCopy(countingAllocator, mStr);
...
...
At start, number of allocations: 0
after 1st string: 1
after 2nd string: 2
after 3rd string: 4
After creation, number of allocations: 5
...
これにより、CFStringCreateMutableCopy で 2 回割り当てが行われていることがわかります。アロケーターの実装でわかるように、このアロケーターは保持ではなく、実際の割り当て時にカウントします。よって、2 回割り当てが要求されていることがわかります。
3.5 reducedMallocAllocatorExample 関数
さて、もう一方のアロケーターについても見てみます。
AllocatorExample.c > reducedMallocAllocatorExample
void reducedMallocAllocatorExample(void) {
CFStringRef str1, str2, str3;
CFMutableStringRef mStr;
CFAllocatorRef reducedMallocAllocator;
show(CFSTR("Reduced Malloc Allocator Test"));
// 削減メモリアロケーター作成、各 40 バイトの 3 ブロックから開始
reducedMallocAllocator = CreateReducedMallocAllocator(NULL, 40, 3);
// 1
show(CFSTR("At start, number of addt'l allocations: %d"),
NumAdditionalAllocations(reducedMallocAllocator));
str1 = CFStringCreateWithCString(reducedMallocAllocator,
"Hello World", kCFStringEncodingASCII);
str2 = CFStringCreateWithPascalString(reducedMallocAllocator,
"\pHello World", kCFStringEncodingASCII);
mStr = CFStringCreateMutableCopy(reducedMallocAllocator, 0, str1);
str3 = CFStringCreateCopy(reducedMallocAllocator, mStr);
show(CFSTR("After creation, number of addt'l allocations: %d"),
NumAdditionalAllocations(reducedMallocAllocator));
CFStringAppend(mStr, str1);
CFStringAppend(mStr, str1);
CFStringAppend(mStr, str1);
show(CFSTR("After mutations, number of addt'l allocations: %d"),
NumAdditionalAllocations(reducedMallocAllocator));
CFRelease(str1);
CFRelease(str2);
CFRelease(mStr);
CFRelease(str3);
show(CFSTR("After releasing, number of addt'l allocations: %d"),
NumAdditionalAllocations(reducedMallocAllocator));
CFRelease(reducedMallocAllocator);
}
使用するアロケーターと一部が違うことを除けば、countingAllocatorExample と同じです。割り当て数は NumAdditionalAllocations 関数が使われています。1 のアロケーター作成は、前は CreateCountingAllocator(NULL) でしたが、今回は CreateReducedMallocAllocator(NULL, 40, 3) で、他に 2 つの引数が渡されています。
AllocatorExample.c > CreateReducedMallocAllocator
static CFAllocatorRef CreateReducedMallocAllocator
(CFAllocatorRef alloc, CFIndex size, CFIndex numBlocks) {
CFIndex cnt;
rmInfo *info;
CFAllocatorContext context = {0, NULL, NULL, rmInfoFree, NULL,
rmAlloc, rmRealloc, rmDealloc, NULL};
//1
// info ブロックとメモリの作成と初期化
info = CFAllocatorAllocate(alloc, (sizeof(rmInfo) + size * numBlocks), 0);
info->size = size;
// 渡されたサイズをそのまま
info->numBlocks = numBlocks;
// 渡されたブロック数も
info->firstAvailable = -1;
// 利用可能かどうかを -1 に
info->numAdditionalAllocations = 0;
// 割り当て数 0
for (cnt = 0; cnt < numBlocks; cnt++) rmMarkBlockAsAvailable(info, cnt);
// 最初の割り当て分
// メモリ割り当てに使われたアロケーターの保持
info->allocator = alloc ? CFRetain(alloc) : CFRetain(CFAllocatorGetDefault());
// そして info ブロックを格納
context.info = info;
// 最後に、アロケーターを作成して返す
return CFAllocatorCreate(alloc, &context);
}
まず、1 で、コンテキスト用の構造体が作られています。今回は、info の独自の構造体を使うため、info 解放用のコールバックが rmInfoFree という独自関数になっています。それ以外は前の説明でわかると思います。
次に、info 用の構造体を割り当てています。このとき、指定サイズが rmInfo のサイズと引数で指定されたブロック数を足したものになっていることに注意してください。つまり、このサンプルの呼び出しでは、構造体サイズ +(40 × 3 で)120となります。rmInfo は以下のように定義されています。
AllocatorExample.c > rmInfo 構造体
typedef struct {
CFAllocatorRef allocator;
// 使ったアロケーター
CFIndex size;
// サイズ
CFIndex numBlocks;
// ブロック数
CFIndex firstAvailable;
// 利用可能か
CFIndex numAdditionalAllocations; // どのぐらい付加メモリが割り当てられたか
void *mem[0];
// 割り当てたメモリブロックの先頭
} rmInfo;
この分とこの後に指定された余分なブロックが付いたメモリが割り当てられたわけです。この各フィールドを初期化していますが、rmMarkBlockAsAvailable が呼ばれています。
AllocatorExample.c > rmMarkBlockAsAvailable
static void rmMarkBlockAsAvailable(rmInfo *info, CFIndex blockNum) {
// このブロックを利用可能リストの先頭に入れる
rmNextBlockNum(info, blockNum) = info->firstAvailable;
info->firstAvailable = blockNum;
}
rmNextBlockNum はマクロで次のように定義されています。
AllocatorExample.c > rmNextBlockNum
#define rmNextBlockNum(info, blockNum)
*(int *)(((unsigned char *)&(info->mem)) + (blockNum * info->size))
CreateReducedMallocAllocator からは、blockNum が 0 から 2 まで 3 回呼ばれることになります。最初の firstAvailable は -1 が入れられていたので、mem の最初にそれが入れられ、firstAvailable には 0 が入れられることになります。1 の時は、mem の size 分だけ後に 0 が入れられ、firstAvailable には 1 が入れられます。最後に、mem の size かける 2 分だけ後に 1 が入れられ、firstAvailable には 2 が入ります。
CreateReducedMallocAllocator に戻って説明すると、次に、info を割り当てたアロケーターを info 自体に格納しています。そして info をコンテキスト構造体に入れて、最後にアロケーターを作成して返します。
info の解放用に関数が設定されましたが、それを見ておきます。
AllocatorExample.c > rmInfoFree
void rmInfoFree(const void *actualInfo) {
rmInfo *info = (rmInfo *)actualInfo;
CFAllocatorRef allocator = info->allocator;
CFAllocatorDeallocate(allocator, info);
CFRelease(allocator);
}
このように、info 内に格納されたアロケーターを使って、割り当て解除をして、そのアロケーターを解放しているだけです。
さて、アロケーター自体の動作を定義するコールバックを見ていきます。まず割り当てコールバックからです。これを理解する前に、このアロケーターは、まず最初に指定されたサイズと数のブロックを作成し、そのブロックが利用できる間は、そのブロックを使い、それが利用できない場合は、格納してあるアロケーターを使って、別に割り当てを行う、ということを覚えておいてください。
AllocatorExample.c > rmAlloc
static void *rmAlloc(CFIndex size, CFOptionFlags hint, void *actualInfo) {
rmInfo *info = (rmInfo *)actualInfo;
if ((info->firstAvailable == -1) || (size > info->size)) {
// 利用可能分がないかサイズが大きい
// 利用可能なものがない、付加メモリ割り当て
info->numAdditionalAllocations++;
// 付加割り当てを加算
return CFAllocatorAllocate(info->allocator, size, hint);
// 渡されたサイズを割り当てて返す
} else {
void *ptr = rmBlock(info, info->firstAvailable);
// ブロックから使う
rmMarkFirstAvailableAsUnavailable(info);
// ブロックを利用不能に
return ptr;
}
}
まず、最初に実際に渡された info を代入します。次に info の firstAvailable が -1 か、あるいは、size が info の 1 ブロックより大きいかどうかを調べます。そうなら、info の付加割り当てを加算し、渡されたサイズを info に格納してあるアロケーターを使って、割り当てます。そうでなければ、ブロックを 1 スロット利用します。rmBlock はマクロで次のようになります。
AllocatorExample.c > rmBlock
#define rmBlock(info, blockNum)
(((unsigned char *)&(info->mem)) + (blockNum * info->size))
渡されたブロックを指すポインタを計算するものです。また、rmMarkFirstAvailableAsUnavailable は以下のようになっています。
AllocatorExample.c > rmMarkFirstAvailableAsUnavailable
static void rmMarkFirstAvailableAsUnavailable(rmInfo *info) {
// これは最初のブロックに対してだけ起こる、そのためリストを解放して返す
info->firstAvailable = rmNextBlockNum(info, info->firstAvailable);
}
rmMarkBlockAsAvailable で、順番に動かしていたものを逆に動かしているだけです。次に再割り当てコールバックを見てみます。
AllocatorExample.c > rmRealloc
static void *rmRealloc
(void *oPtr, CFIndex size, CFOptionFlags hint, void *actualInfo) {
rmInfo *info = (rmInfo *)actualInfo;
if (isRMBlock(info, oPtr)) {
// ブロック内にあるか
void *ptr;
if (size <= info->size) return oPtr; // 簡単な場合 ...
ptr = CFAllocatorAllocate(info->allocator, size, hint);
// 新しく付加割り当て
memmove(ptr, oPtr, info->size);
// データ移動
rmMarkBlockAsAvailable(info, rmBlockNumber(info, oPtr));
// 利用可能に戻す
info->numAdditionalAllocations++;
// 付加割り当てを増分
return ptr;
} else {
// ブロック外なら格納してあるアロケーターを使う
return CFAllocatorReallocate(info->allocator, oPtr, size, hint);
}
}
割り当てを理解していれば、簡単です。まず、isRMBlock でブロック内にあるかを調べます。
AllocatorExample.c > isRMBlock
#define isRMBlock(info, ptr)
(ptr >= (void *)rmBlock(info, 0)
&& ptr <= (void *)rmBlock(info, info->numBlocks))
これは単にポインタが、mem の最初と最後の間にあるか調べているだけです。ブロック外なら、付加割り当てされたものなので、格納しているアロケーターを使います。そうでなければ、まずサイズを調べます。サイズがブロックサイズ内なら、別に新しくメモリを割り当てる必要はないのでそのままにします。そうでなければ、ブロックから割り当てるのは無理なので、別アロケーターを使った割り当てに切り替えます。最後に、割り当て解除コールバックを見てみます。
AllocatorExample.c > rmDealloc
static void rmDealloc(void *ptr, void *actualInfo) {
rmInfo *info = (rmInfo *)actualInfo;
if (isRMBlock(info, ptr)) {
// ブロックの1つ、それを利用可能に戻す
rmMarkBlockAsAvailable(info, rmBlockNumber(info, ptr));
} else {
// ブロックでない、そのため直接割り当て解除する
info->numAdditionalAllocations--;
CFAllocatorDeallocate(info->allocator, ptr);
}
}
これは簡単です。単にブロック内ならそれを利用可能にし、ブロック外なら格納しているアロケーターで割り当て解除するだけです。
これで、このアロケーターについても理解できました。これで、最初の起動例についてもわかると思います。せっかくなので、付加的なコードを追加してみましょう。コールバック関数それぞれや各段階に、show 関数を加えてみます。ややこしいので、個々の追加は説明しません。
Reduced Malloc Allocator Test
At start, number of addt'l allocations: 0
rmAlloc : self
after str1: 0
rmAlloc : self
after str2: 0
rmAlloc : self
rmAlloc : another
after mStr: 1
rmAlloc : another
After creation, number of addt'l allocations: 2
after mStr 1st Append: 2
rmAlloc : another
rmDealloc : another
after mStr 2nd Append: 2
After mutations, number of addt'l allocations: 2
rmDealloc : self
after str1: 2
rmDealloc : self
after str2: 2
rmDealloc : another
rmDealloc : self
after mStr: 1
rmDealloc : another
After releasing, number of addt'l allocations: 0
ここで、self はブロックからの割り当て、another は格納アロケーターからの割り当てです。str1、str2 は self、mstr は self と another、str3 は another から割り当てられていて、結果として付加ブロックが 2 であることがわかります。mStr の最初の変更はそのままで、2 番目の時により大きなブロックが割り当てられ、最初のブロックが割り当て解除され、str3 のときはそれがそのまま使われていることがわかります。解放のときは str1、str2 は self から、mstr が self と another から、str3 は another から解放されています。
4 まとめ
独自の CFAllocator を作って使用することは、通常はあまりないと思います。ただ、メモリ関係で問題が起こったとき、このサンプルで見たように、独自のアロケーターを作ることで、割り当ての数やログ出力をさせて、アプリケーションにおける CF オブジェクトの割り当て状態をチェックして、どのタイミングで割り当てや解除が行われているかを調べることが可能です。
また、独自のアロケーターを利用する場合には、メモリ管理が面倒な場合や、一時的に多量のオブジェクトを割り当て解放する場合があります。独自のアロケーターで特定のゾーンに割り当てます。あとは、すべて必要なくなった時に、そのゾーンそのものを廃棄します。こうすれば、多量の CFRelease() を記述する必要はありません。
この考え方をさらに進めれば、NSAutoreleasePool のように、プログラム実行の特定の段階ごとに、一時オブジェクト用アロケーターを作り、すぐに必要なくなると判っているものはこれで特定のゾーンに割り当て、後でそのゾーンそのものを廃棄します。存続が必要なものはデフォルトのゾーンに作成しメモリ管理します。こうすれば、複数の関数や実行単位にまたがるローカル変数風の、後始末を心配しないで使える変数やオブジェクトを作ることができるでしょう。
割り当てるメモリが少ないことが判っているプログラムでは、推奨はできませんが、そのプロセス内だけで使うものに関しては、メモリ管理そのものをしないことも可能です。プロセスのメモリは終了後に廃棄されるので、動作中にメモリを余分に消費しますが、量がしれているなら心配することもありません。外部に渡したりや数が限定できない場合などを除けば、少量のメモリ漏れにはこせこせしないというのも、公開しない作業用プログラムを作るときには良い態度かもしれません。実際、string サンプルでは、CFRelease() の呼び忘れがあります。そんなもんです。心配なら、ワーストケースだけでもチェックしておけば大丈夫です。
フレームワーク等の呼び出し先で何が行われているか不安だとしても、きちんと設計されたフレームワークは、そのアプリケーションがアクセスできるメモリだけに新規オブジェクトを作成することでしょう。そういう設計であれば、上述のように、メモリ保護をしなくてもアプリケーション用のメモリはクリアされるので大丈夫です。たとえば、OS 9 以前の入力メソッドコンポーネントは、システムの全アプリケーション共通領域に単一のインスタンスが作られて使われていました。しかし、OS X 以降では、各アプリケーションに割り当てられたメモリ内にコンポーネントインスタンスが作られています。メニュー位置が変わらず、単一インスタンスのように見えても、各アプリケーション別々のインスタンスで動作しているわけです。OS 9 では共有メモリを使うため、ひとつのアプリケーションがクラッシュし、それに入力メソッドが巻き込まれた場合、全アプリケーションやシステム動作に影響を与えてしまいました。OS X ではこのような事は起こらないはずです。このように、OS X では、アプリケーションがアクセスできるメモリが制限され、それによってメモリ保護が実現されています。もし、メモリ管理を無視して他に影響が波及するなら、利用しているフレームワークのどれかがメモリ保護を前提にしてきちんと設計されていない、ということになります。
管理人:神吉 秀典 E-mail: