それはどこのメモリ領域じゃ? deleteに引数がないなんて……!

CやC++を使っている際、ある特定のメモリ領域からアロケートしたい、ということがある。
例えばファイルに関するものはファイルアロケータから、とか。
また、デバッグで使うようなメモリならデバッグアロケータから、とか。
Cならmallocとfreeを変えるかフラグ式にすれば良いんだけど、
C++だとさて困ったことがある。
newには引数を追加できるけれどdeleteには引数が追加できないのだ。
これはdeleteに引数を要するなんてこと自体、設計がおかしい、という思想による。(「C++の設計と進化」参考のこと)

こういうとき、どうすれば良いだろうか?
一つの解法としてクラス単位にoperator newとoperator deleteを宣言する、という手がある。

class Bar
{
public:
    static void* operator new (size_t size) throw() {
        return 独自のalloc(size);
    }
    static void operator delete (void* mem) throw() {
        独自のfree(mem);
    }
private:
    int value_;
};

しかし、複数のアロケータがある場合や膨大なクラスがある場合、これを逐一書いていくのは大変だし、
継承した場合に足かせになることもある。


場合によっては、同じクラスだが別の領域からとりたいということもあるかもしれない。


Hoge* hoge = new (kHogeArea) Hoge();
みたいに書けたら、指定した領域からとることができる。
でも、
delete (kHogeArea) hoge;
とは書けない。orz...


じゃ、どうするべきか?


引数なしのdeleteでもdeleteできるようなメモリブロックにする。
一つの案として、メモリのアドレスで振り分けることができる。
mallocのメモリ領域は多くの場合固定アドレスになるだろうから、開始アドレスから終了アドレスまでに入っていれば該当のallocでされたものだと判断できる。
ただし、この場合、以下のようになりかねない。

void operator delete (void* mem) throw()
{
    if (isFooAreaMemory(mem)) {    // mem がFooAreaの開始アドレスから終了アドレスの中にあるか調べる
        FooAreaFree(mem);
        return;
    }
    if (isBarAreaMemory(mem)) {
        BarAreaFree(mem);
        return;
    }
    if (isHooAreaMemory(mem)) {
        HooAreaFree(mem);
        return;
    }
    if (isHeeAreaMemory(mem)) {
        HeeAreaFree(mem);
        return;
    }

}

もう一つの方法として、malloc/free実装にはメモリブロックに対してアロケータの情報を蓄えられるものがある。
例えば、dlmallocはその典型で、
FOOTERSを定義するとメモリ領域を余分に使うようにはなるが、
どの領域に対してfreeを行っても自動で該当の領域から解放してくれるようになる。

以下の定義をdlmallocのmalloc.cで有効にする。
#define MSPACES 1
#define ONLY_MSPACES 1
#define FOOTERS 1

サンプル。
dlmallocのMSPACESを使い2つのアロケータをつくり、
それぞれの領域からクラスをnewしている。
FOOTERSを宣言しない場合にはAbortしてしまうだろう。

アロケータ領域を適切に分けるテクニックはメモリ資源が酷く少ない時などにも
・メモリの断片化管理
・メモリのスコープ管理
などに非常に役立つ。

#include <cstdio>
extern "C" {
#include "malloc.h"
}

using namespace std;

namespace {
enum {
    kFooAreaSize = 0x10000,
    kBarAreaSize = 0x10000,
};
    
mspace foo_;
mspace bar_;
unsigned char fooArea_[kFooAreaSize];
unsigned char barArea_[kBarAreaSize];
}

void InitializeFooArea()
{
    foo_ = create_mspace_with_base(fooArea_, kFooAreaSize, 0);
}

void InitializeBarArea()
{
    bar_ = create_mspace_with_base(barArea_, kBarAreaSize, 0);
}

enum {
    kFooArea,
    kBarArea,
};

void* operator new (size_t size, unsigned int newFlag) throw()
{
    printf("new!\n");
    if (newFlag & kFooArea) {
        return mspace_malloc(foo_, size);
    }
    if (newFlag & kBarArea) {
        return mspace_malloc(bar_, size);
    }
    return 0;
}

void operator delete (void* mem) throw()
{
    printf("delete!\n");
    mspace_free(foo_, mem);
}

class Bar
{
public:
    static void* operator new (size_t size) throw() {
        printf("operator new\n");
        return mspace_malloc(bar_, size);
    }
    static void operator delete (void* mem) throw() {
        printf("operator delete\n");
        mspace_free(bar_, mem);
    }
private:
    int value_;
};

class Hoge
{
private:
    int value_;
};

int main()
{
    InitializeFooArea();
    InitializeBarArea();
    
    Bar* bar = new Bar();
    
    delete bar;
    
    {
        Hoge* hoge = new (kBarArea) Hoge();
        delete hoge;
    }
    
    {
        Hoge* hoge = new (kFooArea) Hoge();
        delete hoge;
    }

    return 0;
}

C++の設計と進化

C++の設計と進化