豪鬼メモ

一瞬千撃

alloc_alignedの野良実装

バイス等とやり取りするためや、他の様々な理由で、ある数の倍数にアラインメントされたメモリ領域を動的に確保する必要が生じることがある。mallocフレームワーク上でそれを行うのが標準C++のalloc_alignedという関数なのだが、それはすべての処理系でサポートされているわけではない。そんな場合に自前のフォールバックがあると便利だ。
f:id:fridaynight:20210508114528j:plain


とある作業で、512の倍数のアドレスにアラインメントされた領域を確保することが必要になった。C++11標準から導入されたalloc_alignedという関数がそれにうってつけなのだが、Visual C++ではサポートされていないことに気づいた。ならば、自分で書いてみよう。

xmallocalignedは、第1引数にアラインメントサイズ、第2引数に要求サイズを受け取り、アラインメントされたポインタを返す。失敗した際にはnullptrを返す。

void* xmallocaligned(size_t alignment, size_t size) {
  void* ptr = malloc(size + sizeof(void*) + alignment);
  if (ptr == nullptr) return nullptr;
  char* aligned = (char*)ptr + sizeof(void*);
  aligned += alignment - (intptr_t)aligned % alignment;
  std::memcpy(aligned - sizeof(void*), &ptr, sizeof(void*));
  return aligned;
}

内部では、標準のmallocを呼ぶが、要求サイズにアラインメントとvoid*のサイズを足した分だけの領域を確保しておく。そして、確保したアドレスを格納するためのvoid*の分だけポインタをずらしてから、さらにアラインメントの倍数になるようにポインタをずらす。こうすることで、返却アドレスはアラインメントの倍数になりつつ、その前にvoid*の分だけ自由に使える領域ができる。そこに元のアドレスを書き込んでおく。

こうして確保したアラインメント済みの領域は、専用の関数xfreealignedで解放せねばならない。なぜなら、元のアドレスを復元してからfreeを呼ばねばならないからだ。

void xfreealigned(void* ptr) {
  if (ptr == nullptr) return;
  void* orig = nullptr;
  std::memcpy(&orig, (char*)ptr - sizeof(void*), sizeof(void*));
  std::free(orig);
}

ところで、Windowsでは、同じようなフォールバック実装として_aligned_mallocと_aligned_freeがある。確保と解放が対になっているのは、元のアドレスを退避してからポインタをずらすからだろう。そう、確保と解放を専用の対の関数でできるなら、こんな実装は小学生でもできるのだ。にもかかわらずWindowsで標準alloc_alignedが実装されていないのは、標準仕様では、確保した領域を標準freeで解放できるようにしなければならないと定義しているからである。これを満たすには、標準のメモリアロケータ自体を書き換えてアラインメント機能を実装せねばならない。後付けでそれをやるのは厳しいだろう。alloc_alignedのような基本的なAPIWindowsでサポートしていないというのは意外だと最初は思ったが、これはC++標準仕様の問題だ。移植性を重視しているはずの標準仕様で、下層のメモリアロケータに厳しすぎる前提を課している。free_alignedなる対関数で解放させる仕様にしておけば良かったのだ。ちなみに、細かい話だが、alloc_alignedと_aligned_mallocは引数の順序が逆なので、気づかずに謎バグを生みやすいので注意だ。

DarwinMac OS)でも現状ではalloc_alignedは実装されていないらしい。よって、この野良実装はそこで役立つ。


GCCでは標準alloc_alignedを使い、VC++では_aligned_mallocを使えばよいので、実際にこの野良実装を使う環境はMac OSくらいだろう。ただ、一般的な処理系への移植性を考えたプログラムではこの手のフォールバックは持っておいた方がいい。

ところで、何でこんなことをやっているかというと、TkrzwのダイレクトIO対応機能を書いているからだ。LinuxのO_DIRECTによるIOでは512バイトにアラインしたデータの転送しかできないのだ。実装はほぼ完成しているので、近日公開予定である。図書館が緊急閉館なので目黒天空庭園で作業しているのだが、意外に捗る。