未初期化な領域とアラインメント

http://d.hatena.ne.jp/Cryolite/20051021#p1
の問題に対する解答.
まず大前提として,未初期化な領域に配置構文 new を用いてオブジェクトを構築する手法は潜在的な危険が多く,本当にそれが必要な場合以外は用いないという方針が基本であることを確認しておいてください.
まず最初に自動変数として(スタック上に)確保した char の配列に任意のオブジェクトを構築する場合についてです.

class MyClass
{
  .....
};

int main()
{
  char buf[sizeof(MyClass)];
  MyClass *p = static_cast<MyClass *>(static_cast<void *>(buf));
  ::new (p) MyClass();

  ..... // #1

  p->~MyClass();
}

上のコードはアラインメントの問題を抱えています.C++ のオブジェクトはその型毎に構築できるアドレスに制限があり(これをアラインメントの要求といいます),制限を満たさないアドレスにオブジェクトを構築すると未定義の動作を引き起こします.上のコードでは,buf は char の配列であり,そのアドレスは char が構築できるアドレス上に配置されていますが,MyClass のオブジェクトが配置できるアドレスである保証はどこにもありません.そのアドレスに,配置構文 new で MyClass のオブジェクトを構築すると未定義の動作を引き起こします.
Boost には 上記のようなアラインメント周りの問題を解決するパーツが用意されています.TypeTraits ライブラリの boost::alignment_of, boost::aligned_storage です.これを用いることでおおよそのアラインメント周りの問題は解決することができます.
(注意: Boost 1.33.0 のドキュメントは boost::type_with_alignment と boost::aligned_storage の記述がライブラリ著者のタイポで逆になっています.)
また,http://d.hatena.ne.jp/Cryolite/20051021 のコメント欄で郵便はみがきさんが指摘されているように,#1 の部分で例外が送出された際に p に構築されている MyClass のオブジェクトのデストラクタが呼び出されないという問題も生じます.これは RAII,try-catch,ScopeGuard などを用いて対処することができます.ここでは,郵便はみがきさんによる RAII による手法をそのまま採用させてもらうことにします.
上記に挙げた問題を修正したコードが以下です.

#include <new>
#include <boost/utility.hpp>
#include <boost/type_traits/alignment_of.hpp>
#include <boost/type_traits/aligned_storage.hpp>

class MyClass
{
  ...
};

template<class T>
class scoped_destroy : boost::noncopyable
{
public:
  explicit scoped_destroy(T* p) : ptr_(p) {}
  ~scoped_destroy(){ptr_->~T();}
  T& operator*()const{return *ptr_;}
  T* operator->()const{return ptr_;}
  T* get()const{return ptr_;}
private:
  T* ptr_;
};

int main()
{
boost::aligned_storage<
    sizeof(MyClass), boost::alignment_of<MyClass>::value
  >::type buf;
scoped_destroy<MyClass> p(::new (static_cast<void*>(&buf)) MyClass());

... // p.get(), p->(), *p を用いて構築したオブジェクトを利用する

// ここで明示的なデストラクタの呼び出しは不要.
}

次にフリーストア(ヒープ)上に確保した領域上に配置構文 new でオブジェクトを構築する場合です.

class MyClass
{
  .....
};

int main()
{
  MyClass *p = static_cast<void *>(::new char[sizeof(MyClass)]);
  ::new (p) MyClass();

  ..... // #1

  p->~MyClass();
  delete[] p;
}

こちらは元々の出題ミスで色々な問題が複合してしまっています.特に,最後の

delete[] p;

は char の配列として new で確保した領域を MyClass の配列として delete しているため,未定義の動作を引き起こします.
一方で自動変数として確保した領域上にオブジェクトを構築する場合と異なり,こちらの配置構文 new は安全です.というのも,char の配列を確保する new が返すアドレスは,その配列のサイズ以下のあらゆる型のオブジェクトが安全に構築できるアドレスであることが規格上要求されているからです.
もちろん,先と同様 #1 で例外が投入された場合の例外安全性の問題は存在するので,この問題も先と同じ方法で対処します.

#include <new>
#include <boost/utility.hpp>
#include <boost/scoped_array.hpp>

class MyClass
{
  ...
};

template<class T>
class scoped_destroy : boost::noncopyable
{
public:
  explicit scoped_destroy(T* p) : ptr_(p) {}
  ~scoped_destroy(){ptr_->~T();}
  T& operator*()const{return *ptr_;}
  T* operator->()const{return ptr_;}
  T* get()const{return ptr_;}
private:
  T* ptr_;
};

int main()
{
boost::scoped_array<char> buf(new char[sizeof(MyClass)]);
scoped_destroy<MyClass> p(::new(static_cast<void*>(buf.get())) MyClass());

...

}

以上のようになります.

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

以下,自動変数の 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 のオブジェクトをデストラクトするように対処しておかなければなりません.