これはブログ記事(http://in-neuro.hatenablog.com/entry/2018/03/26/125059 )のダイジェスト版です。
TL;DR
C++17ではデフォルトより大きなアライメント要求を持つ型はアライメント指定new
が優先的に呼ばれる。これで動的メモリ確保でも(通常より大きな)アライメント要求が通るようになった。
背景
アライメントまわりのことを調べていたらC++17でaligned_alloc
とnew
の新しいオーバーロードが入っていたことに気づき、少し規格書(N4659)と元になったP0035R4にあたってみることにしました。§番号はN4659に対応します。
C++11 alignas
の問題点
alignas
で通常より大きなアライメント指定をした変数や構造体は、そのメモリ境界にアラインされるようになります。
alignas(32) int i;
struct alignas(32) double4 {
double elems[4];
};
……ただし、動的確保した場合はこの限りではなく、new
の結果はアライメントが保証されていません。new
の結果はデフォルトで要求されるアライメントのうち一番大きいものに合わせられていますが、デフォルトより大きい値に関してはその限りではありません。
このため、posix_memalign
や_aligned_malloc
などの処理系固有の関数を使いわけるか、大きめに確保しておいてstd::align
を使ってアライメントが揃っている範囲だけを取り出して使う、というような策を取らざるを得ませんでした。
C++17におけるアライメントサポート
C++17ではめでたく動的確保時にアライメントを指定できる機能が追加されました。C11で<stdlib.h>
に入っていたaligned_alloc
が<cstdlib>
に追加され(§ 23.10.11)、new
にはstd::align_val_t
を取るオーバーロードが追加されました(§ 21.6.1)。
std::align_val_t
は以下のように定義されています。
namespace std {
enum class align_val_t : size_t {};
}
enum class
ですが、これは意図しない型変換を防ぐための(opaque typedef相当の)ものなので、特にenum値が定義されているわけではありません。
new
とdelete
にはこれを取るオーバーロードが追加されました。
void* operator new(std::size_t, std::align_val_t);
void* operator new[](std::size_t, std::align_val_t);
void operator delete(void*, std::align_val_t);
void operator delete[](void*, std::align_val_t);
void operator delete(void*, std::size_t, std::align_val_t);
void operator delete[](void*, std::size_t, std::align_val_t);
align_val_t
として不適当な値を渡した場合、その動作は未定義です(§ 21.6.2)。
ではこれらのオーバーロードを手動で指定して呼び分けねばならないかというと、そうでもありません。もし通常のnew
の結果が満たすものより大きなアライメントを要求する型についてnew
を呼びだせば、自動的にalign_val_t
版が、引数にalignof(T)
が渡されて呼ばれます(§ 21.6.2.1 Effects、§ 21.6.2.2 Effects)。
「通常のnew
の結果よりも大きなアライメントを要求する型(new-extended alignment)」かどうかは、定義済みマクロ__STDCPP_DEFAULT_NEW_ALIGNMENT__
の値を超える大きさのアライメント要求を持っているかどうかで判定されます(§ 6.6.5)。このマクロはstd::size_t
型の整数リテラルに展開され(§ 19.8)、それより大きいアライメント指定を持つ型にはnew(align_val_t(alignof(T)))
が呼ばれるようになります。
注意点
ところで、クラスはnew
・delete
演算子をオーバーロードできます。
そのようなクラスは、通常のnew
の結果より大きなアライメントを要求する場合でも、今まで通りクラス特異的なnew
が呼ばれます。
ユーザー定義new
があった場合、アライメント指定に関わらずグローバルなnew
よりもそちらが優先され(P0035R4でのNitty-grittyで言及されています。§ 21.6.2.1 Replaceable がこれに対応すると認識しています)、もしユーザー定義new
にアライメント指定を取るバージョンとそうでないバージョンがあった場合は、その中でアライメント指定を取るバージョンが優先的に考慮されます。