2011年12月27日火曜日

アライメント

主にJavaを触っているのだが、よく考えればメモリー管理はよろしくやってくれるわけで・・・。
アドレスも見れないし見る必要がないコンセプトなのでいいやと思いつつも、何故かC++版を
書いてみたい衝動に駆られ、久しぶりにC/C++コンパイラーを起動した。
すぐに終わるだろうと考えていたが、環境がUbuntu-64bit(Android-Gingerbread以上)なことを
すっかり忘れていたため、予想外に時間が掛かってしまった。
C/C++関連は32bitOSにどっぷりと慣れていたからなぁ・・・。

// アライメントの計算
uintptr_t alignment(uintptr_t rawAddress, size_t align){
    align--;
    return (rawAddress + align) & ~align;
}

void* allocateAligned(size_t size, size_t align){
    // 2の羃乗でない場合はアサート
    assert(((align - 1) & align) == 0);
    // 実際に確保するサイズ(actual_size = request_size + (align - 1) + offset_size)
    size_t actualSize = size + (align - 1) + sizeof(void*);
    // アラインされていないメモリ確保
    void* pRawAddress = malloc(actualSize);
    if(pRawAddress == NULL)
        return NULL;
    // オフセットアドレスに生アドレスを書き込む
    uintptr_t rawAddress = reinterpret_cast<uintptr_t>(pRawAddress);
    uintptr_t alignedAddress = alignment(rawAddress + sizeof(void*), align);
    ptrdiff_t offsetAddress = alignedAddress - sizeof(void*);
    void** ppOffsetAddress = reinterpret_cast<void**>(offsetAddress);
    *ppOffsetAddress = pRawAddress;
    // アラインされたアドレスへのポインタを返す
    return reinterpret_cast<void*>(alignedAddress);
}

void Allocator::freeAligned(void* p){
    assert(p != NULL);
    uintptr_t alignedAddress = reinterpret_cast<uintptr_t>(p);
    uintptr_t offsetAddress = alignedAddress - sizeof(void*);
    void** ppOffsetAddress = reinterpret_cast<void**>(offsetAddress);
    // アラインされていないアドレスへのポインタで解放
    free(*ppOffsetAddress);
}

64bitなのでポインターサイズは8bytes。
32bitなら気にせず unsigend int で済ましても問題がなかったところ。
uintptr_t、ptrdiff_t、size_t が環境によって自動的に変わるため、これを利用すればいい。

※オフセット値を2bytesで保持していたが、アドレスを直接持つように変更