まるで面白くないアラインメントに関する考察

以下,自動変数の char 配列として確保した領域に,配置構文 new でオブジェクトを構築する方法に対する安全性の考察をやっています.ただし,C++ におけるアラインメントの考察を,実際のハードウェアなどの概念から完全に隔離された,厳密な規格の記述だけでやっているため(ただし,1つだけ C++ の規格からではどうしても明らかでない仮定を置いています<4>),滅茶苦茶回りくどい上にムダに抽象的なので読むのはあんまりお勧めできません.

<1>:object representation

任意の型 T のオブジェクトは,サイズが sizeof(T) である連続した char の配列に対応するストレージ を取得している.これを T の object representation と呼ぶ.


3.9 Types
4 The object representation of an object of type T is the sequence of N unsigned char objects taken up by the object of type T, where N equals sizeof(T). The value representation of an object is the set of bits that hold the value of type T. For POD types, the value representation is a set of bits in the object representation that determines a value, which is one discrete element of an implementation-defined set of values.

<2>:アラインメントの要求 (alignment requirement)

オブジェクトは好きなアドレスに勝手に配置できるわけではない.各型ごとに,その型のオブジェクトが構築できるアドレスに制限がある.これをアラインメントの要求 (alignment requirement) という.


3.9 Types
5 Object types have alignment requirements (3.9.1, 3.9.2). The alignment of a complete object type is an implementation-defined integer value representing a number of bytes; an object is allocated at an address that meets the alignment requirements of its object type.

<3>:オブジェクトを配置可能なアドレスの差

十分大きな char の配列 place[N] を想定する.ある型 T を考え,place[a] から place[a + sizeof(T) - 1] までが T に対するアラインメントの要求を満たしているとする.このとき,ある正の整数 A が存在し,place[a + A] から place[a + A + sizeof(T) - 1] もまた T のアラインメントの要求を満たした T の object representation となるようにすることができる.少なくとも,そのような A として sizeof(T) がある.

説明

ある型 T の配列 t[N] を考える.配列 t の object representation は,(5.3.3/2)からサイズが sizeof(T) * N の char の配列である.今,この char 配列を char a[sizeof(T) * N] と表現する.T のオブジェクト t[0] の object representation は a[0] から a[sizeof(T) - 1] までの連続した char 配列であり,また t[1] の object representation は a[sizeof(T)] から a[2 * sizeof(T) - 1] までの連続した char 配列である.このように,sizeof(T) は上記にある正の整数 A の性質を満たす

<4>:<3>の整数に関する仮定

ある型 T に対して,<3>で述べた条件を満たす2つの正の整数 A と B があったとする.このとき, A は B の約数か倍数でなければならない.

説明

この主張は C++ の言語規格としては明示されていないものの,一般のアラインメントの概念として極めて妥当である,と考える.また,C の言語規格 ISO/IEC 9899:1999 の記述とも合致する.

<5>:アラインメントの定義

(ここで定義している「アラインメント」は,通常あるようなアラインメントの定義とも,C++ の規格にある定義とも異なるものですが,それらと実質同等な定義になります.)
任意の型 T に対して,<3>の条件を満たし,なおかつ<3>の条件を満たす他の異なる正の整数の倍数でないような正の整数がただ一つ存在する.T に対するこの正の整数を T のアラインメントと定義する

説明

<3>から,このような正の整数が少なくとも1つ存在することは明らか.また<4>の仮定を認めるならば,このような正の整数がただ1つしかないことも明らか.

<6>:アラインメントが等しい型とアラインメントの要求の互換性

2つの型 T と U を考え,これら2つの型のアラインメントが等しいとする.今,十分大きな char の配列を考える.place[a] から place[a + sizeof(T) - 1] までが型 T のアラインメントの要求を満たした型 T のオブジェクトの object representation となれるとき,place[a] から place [a + sizeof(U) - 1] は型 U のアラインメントの要求を満たした型 U のオブジェクトの object representation となれる.

説明

place[a] から place[a + sizeof(T) - 1] までの連続した char の領域が T のアラインメントの要求を満たした T のオブジェクトの object representation となれるが,place[a] から place[a + sizeof(U) - 1] までの連続した char の領域が U のアラインメントの要求を満たさず U のオブジェクトの object representation とはなれないと仮定する.このとき,アラインメントの定義から T の object representation と U の object representation とに同時になれるようなストレージの領域が全く存在しないことが分かる.しかしこれは(あるサイズ以下の)任意の型のオブジェクトを構築できるストレージが存在することを示唆する規格である(3.7.3.1/2)や(5.3.4/10)に反する.従って背理的に題意が示される.

<7>:「緩い」アラインメントの要求

型 T のアラインメントが型 U のアラインメントの約数であるとき,U に対するアラインメントの要求を満たすアドレスは T に対するアラインメントの要求も満たす.

説明

多分明らか.

<8>:アラインメントと sizeof の値

任意の型 T に対して,そのアラインメントは sizeof(T) の約数となる.

説明

アラインメントの定義から明らか.

<9>:アラインメント計算 - の実装

型 T に対して,以下のような構造体を定義する.

struct alignment_hack
{
  char c;
  T t;
};

このとき sizeof(alignment_hack) - sizeof(T) は T のアラインメントの倍数となる.

説明

図の方が分かりやすいと思うので図で説明.
http://park7.wakwak.com/~cryolite/hatena/20051103.html
図から明らかなように sizeof(alignment_hack) - sizeof(T) は<3>で述べた正の整数の条件を満たす.<5>のアラインメントの定義から,この値は T のアラインメントの倍数でなければならない.
boost::alignment_of の実際の実装は,この整数と sizeof(T) のうち小さいほうを取るという実装になっている.論理的には boost::alignment_of はアラインメントの倍数を返すが,既知の全ての場合でアラインメントそのものを返すことが確認されている,とドキュメントで述べられている.が,つい最近アラインメントではなくてその倍数を返す例が発見された.
http://thread.gmane.org/gmane.comp.lib.boost.devel/133562

<10>アラインメント保証付き POD 型 - の実装

ある正の整数 A に対して,

boost::type_with_alignment<A>::type

という型は,アラインメントが A の倍数である POD 型となる.ただし,A の値によってはコンパイルできないことがある.

説明

boost::type_with_alignment の実装は,単に全ての fundamental type (char, short, int, long)及び関数ポインタ型,メンバ関数ポインタ型の中から,そのアラインメントが A の倍数(A 自体を含む)でかつアラインメントが最小のものを探しそれを返す.従って,これらの型のうちのどの型のアラインメントの倍数でもないような A を指定すると,要求を満たした型を返せない.このような場合,実装内部では安全のためにコンパイル時エラーを引き起こすようにしている.

<11>アラインメント保証付きストレージ - の実装

以下のように定義される共用体 U

union U{
  char buf[sizeof(T)];
  type_with_alignment<alignment_of<T>::value>::type a;
};

を考える.(コンパイラによって)正しいアドレスに配置された U のオブジェクトは,そのアドレスが

  • T のアラインメントの要求を満たし,かつ
  • sizeof(T) 以上のサイズを持った連続した char の配列の先頭を指す.
boost::alignment_of<S, A>::type

という型はこのような共用体である.

説明

まず,buf, a 共に POD である.メンバが全て POD の共用体は特に POD-union と呼ばれる(9/4).POD-union は 全てのメンバ及び union のオブジェクト自身が等しく共通したアドレスを先頭としているという規格(9.2/16)(9.5/1)がある.従って,(コンパイラによって)正しいアドレスに配置された U のアドレスは,同時に a のアドレスでもあり,従ってそのアドレスは T のアラインメントの要求を満たす.同時に buf のアドレスでもあり,少なくとも sizeof(T) のサイズを持ったストレージの領域を指すアドレスでもある.

<12>placement form operator new

以下の形式の operator new

void* operator new(std::size_t size, void* ptr) throw();

は配置形式の operator new (placement form operator new)と呼ばれ,単に ptr を返しそれ以外には何もしない関数である(18.4.1.3).

<13>単一オブジェクト形式の placement new

(ここで言う「単一オブジェクト形式」とは 配列形式ではない,つまり new (p) T[] という形式ではなくて new (p) T という形式のことを指している.)
型 T に対する placement new は,オブジェクトを構築するための領域を確保するために operator new を(特殊な形式で)呼び出す.そして operator new から戻されるアドレスが T のアラインメントの要求を満たし,かつ要求するサイズを持ったストレージを指していることを前提にして,そのストレージ上にオブジェクトを構築する.


14 [Note: when the allocation function returns a value other than null, it must be a pointer to a block of storage in which space for the object has been reserved. The block of storage is assumed to be appropriately aligned and of the requested size. The address of the created object will not necessarily be the same as that of the block if the object is an array. ]

<14>単一オブジェクト形式の placement new が要求するサイズ

型 T に対する単一オブジェクト形式の placement new が allocator function として呼び出す ::operator new に対して要求するサイズは,厳密に sizeof(T) である.

<15>placement new が allocator function として呼び出す operator new

placement new が呼び出す allocation function は配置形式 operator new である.
特に,単一オブジェクト形式の配置構文 new,

new (p) T(...)

という式が allocator function を呼び出す際の呼び出し構文は

operator new(sizeof(T), p)

である.

<16>配置構文 new に渡すアドレスに対する要求

型 T の単一オブジェクト形式配置構文 new に渡されるアドレスが満たすべき制約は以下である.

  • sizeof(T) 以上のストレージを指しており,かつ
  • T のアラインメントの要求を満たしたストレージを指していること.

逆に,以上の要求を満たしたアドレスを渡せば,そのアドレスに安全にオブジェクトが構築されると考えるのが妥当である.

説明

配置構文 new が渡されたアドレスに対して要求する事項が上記の通りであるのは,先に書いたとおり.逆に,渡されるアドレスが指すストレージのサイズとアラインメントの2つが満たされれば,配置構文 new が安全に機能すると考えるのが妥当である.なぜなら,規格が allocator function (つまり operator new)の戻り値に要求しているのは以上の2つしかなく(3.7.3.1/2),さらに配置構文 new の立場から見れば allocator function (つまり 配置形式 operator new)の戻り値に対する要求というのは,<12>の通りまさに配置構文 new に渡されるアドレスに対する要求そのものとなるからである.以上から,配置構文 new に渡すアドレスが以上の2つの要求を満たしていれば,配置構文 new は安全に機能する.

<17>結論

以下のコードは安全である.

int main()
{
  boost::aligned_strage<
      sizeof(MyClass), boost::alignment_of<MyClass>::value
    >::type storage;
  MyClass *p = static_cast<MyClass *>(static_cast<void *>(&storage));
  new (p) operator MyClass();

  ... // p を通じて,MyClass のオブジェクトを操作する.(#1)

  p->~MyClass()
}

注意:例えば,#1 の部分で例外が投入された際の例外安全性を考慮した場合,郵便はみがきさんが指摘されている通り RAII や try - catch などで p に構築された MyClass のオブジェクトをデストラクトするように対処しておかなければなりません.