これはブログ記事(http://in-neuro.hatenablog.com/entry/2018/03/26/125059 )のダイジェスト版です。

TL;DR

C++17ではデフォルトより大きなアライメント要求を持つ型はアライメント指定newが優先的に呼ばれる。これで動的メモリ確保でも(通常より大きな)アライメント要求が通るようになった。

背景

アライメントまわりのことを調べていたらC++17でaligned_allocnewの新しいオーバーロードが入っていたことに気づき、少し規格書(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値が定義されているわけではありません。

newdeleteにはこれを取るオーバーロードが追加されました。

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)))が呼ばれるようになります。

注意点

ところで、クラスはnewdelete演算子をオーバーロードできます。
そのようなクラスは、通常のnewの結果より大きなアライメントを要求する場合でも、今まで通りクラス特異的なnewが呼ばれます。

ユーザー定義newがあった場合、アライメント指定に関わらずグローバルなnewよりもそちらが優先され(P0035R4でのNitty-grittyで言及されています。§ 21.6.2.1 Replaceable がこれに対応すると認識しています)、もしユーザー定義newにアライメント指定を取るバージョンとそうでないバージョンがあった場合は、その中でアライメント指定を取るバージョンが優先的に考慮されます。

Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account log in.