malloc や同様のものの実装に関する優れた文献がかなりあります。しかし、ここに C++ が含まれていることに気付きました。C++ で独自の実装を作成できることをご存知new
ですかdelete
? 手軽にできる方法として重宝しそうです。
いずれにせよ、必要な特性はワークロード、つまり時間の経過に伴う使用パターンに大きく依存します。malloc と新しい free しかない場合は、明らかに簡単です。1 つまたはいくつかの異なるブロック サイズの malloc しかない場合、それも簡単です。
他の言語では、メモリを連結する機能を利用することである程度の活用が可能ですが、C はそれほどスマートではありません。
malloc の基本的な実装では、データ長、「使用中フラグ」、および割り当てられたメモリを含むヘッダーを割り当てるだけです。次に、malloc はそのスペースの最後に新しいヘッダーを作成し、メモリを割り当て、ポインターを返します。解放すると、使用中フラグがリセットされるだけです。
秘訣は、mallooc と free を大量に実行すると、使用されていない小さな BLOB がすぐに大量に取得され、割り当てが困難になることです。したがって、メモリのブロックをマージするには、ある種のバンプ GC が必要です。
もっと複雑な gc を実行することもできますが、時間がかかることに注意してください。無料で多くの時間を費やしたくありません。
Solaris の malloc 実装に関する興味深い論文があります。これも Solaris で別のmalloc を作成する方法ですが、基本は同じです。そして、ガベージ コレクションに関するウィキペディアの記事を読み、それに従ってより正式な論文をいくつか読んでください。
アップデート
ご存知のように、世代別ガベージ コレクターを確認する必要があります。基本的な考え方は、何かが割り当てられたままになる時間が長ければ長いほど、割り当てられたままになる可能性が高くなるということです。これは、言及した「コピー」GCの拡張です。基本的に、メモリプールの一部に新しいものを割り当て、それを g0 と呼びます。その最高水準点に到達したら、割り当てられたブロックを調べて、まだ使用されているブロックをメモリの別のセクションにコピーし、それを g1 と呼びます。次に、g0 スペースをクリアして、そこに割り当てを開始できます。最終的に g1 は最高水準点に到達し、g0 をクリアして修正し、g1 をクリーンアップして g0 に移動します。完了したら、古い g1 を g0 に、またはその逆に名前を変更して続行します。
秘訣は、特に C では、malloc されたメモリに渡すハンドルが生のポインタであることです。ヒープの大きな薬がなければ、実際に物事を動かすことはできません。
2回目の更新
コメントで、@unknown は「memcpy() だけでは物を動かさないだろう」と尋ねます。そして確かにそうなるでしょう。ただし、次のタイムラインを考慮してください。
警告: これは完全ではなく、テストもされていません。説明のため、娯楽のみを目的としており、明示または黙示の保証はありません。
/* basic environment for illustration*/
void * myMemoryHdl ;
unsigned char lotsOfMemory[LOTS]; /* this will be your memory pool*/
一部のメモリを割り当て解除します
/* if we get past this, it succeded */
if((myMemoryHdl = newMalloc(SIZE)) == NULL)
exit(-1);
malloc の実装では、メモリを作成し、ポインタをバッファに返します。
unsigned char * nextUnusued = &lotsOfMemory[0];
int partitionSize = (int)(LOTS/2);
int hwm = (int) (partition/2);
/* So g0 will be the bottom half and g1 the top half to start */
unsigned char * g0 = &lotsOfMemory[0];
unsigned char * g1 = &lotsOfMemory[partitionSize];
void * newMalloc(size_t size){
void * rtn ;
if( /* memory COMPLETELY exhausted */)
return NULL;
/* otherwise */
/* add header at nextUnused */
newHeader(nextUnused); /* includes some pointers for chaining
* and a field with values USED or FREE,
* set to USED */
nextUnused += HEADERLEN ; /* this could be niftier */
rtn = nextUnused ;
nextUnused += size ;
}
解放されたものもある
newFree(void * aHandle){
*(aHandle-offset) = FREE ; /* set the flag in the header,
* using an offset. */
}
これですべての作業が完了し、最高水準点に到達します。
for( /* each block in your memory pool */ )
if( /* block header is still marked USED */ ) {
memcpy(/* block into other partition */);
}
/* clear the partition */
bzero(g0, partitionSize);
ここで、myMemHdl に保存した元のハンドルに戻ります。それは何を指していますか?(答え、bzero(3) で 0x00 に設定するだけです。)
ここで魔法の出番です。少なくとも C では、malloc から返されたポインタはもはや制御できません。事後に移動することはできません。C++ では、ユーザー定義のポインターのような型を使用すると、これを修正できます。