Alignment
C++ の低レベルの機能の 1 つは、特定のハードウェア アーキテクチャを最大活用するために、メモリ内のオブジェクトの正確な配置を指定できる機能です。 既定では、コンパイラはクラスと構造体のメンバーをそのサイズ値上に配置します。bool
と char
を 1 バイト境界、short
を 2 バイト境界、int
、long
、float
を 4 バイト境界、long long
、double
、long double
を 8 バイト境界上に配置します。
ほとんどのシナリオで、既定の配置は既に最適なので、配置を気にする必要はありません。 しかし、場合によっては、大幅なパフォーマンスの向上、またはメモリの節約を、データ構造にカスタム配置を指定することで達成できます。 Visual Studio 2015 の前は、既定値を超える配列を指定するのに、Microsoft 固有キーワード __alignof
と __declspec(align)
を使用できました。 Visual Studio 2015 以降、C++11 の標準キーワードの alignof
と alignas
を使用して、コードの移植性を最大にする必要があります。 この新しいキーワードは、Microsoft 固有の拡張機能と同じように内部で動作します。 これらの拡張機能に関するドキュメントは、新しいキーワードにも適用されます。 詳細については、「alignof
演算子」と「align」を参照してください。 C++ 標準は、ターゲット プラットフォームに対するコンパイラの既定値よりも小さい境界の配置では、パッキング ビヘイビアーを指定しないため、その場合は Microsoft #pragma pack
を引き続き使用する必要があります。
カスタム配置を使用するデータ構造のメモリ割り当てには、aligned_storage クラスを使用します。 aligned_union クラスは、非単純コンストラクターまたはデストラクターを使用して共用体に関する配置を指定します。
配置とメモリ アドレス
配置は、2 の累乗の数値のアドレスの剰余として表現される、メモリ アドレスのプロパティです。 たとえば、0x0001103F のアドレスを 4 で割ると 3 になります。 このアドレスは 4n+3 に配置されたということができます。4 は選択した 2 の累乗です。 アドレスの配置は、選択した 2 の累乗値に依存します。 同じアドレスの 8 の剰余は 7 です。 配置が Xn+0 の場合、アドレスが X に配置されると言われます。
CPU は、メモリに格納されているデータを操作する命令を実行します。 データは、メモリ内のアドレスによって識別されます。 単一のデータにもサイズがあります。 アドレスがサイズに合わせて配置されている場合は、データが自然に配置されていると呼びます。 そうでない場合は適切に配置されていないと呼びます。 たとえば、識別のために使用されるアドレスが 8 バイトの配置の場合、8 バイト浮動小数点のデータが自然に配置されます。
コンパイラによるデータ アラインメントの処理
コンパイラは、データが適切に配置されないのを防ぐ方法でデータ割り当てを試行します。
単純なデータ型の場合、コンパイラは、データ型のバイト単位のサイズの倍数であるアドレスを割り当てます。 たとえば、コンパイラは、4 の倍数である long
型の変数にアドレスを割り当てて、アドレスの下の 2 つのビットをゼロに設定します。
また、コンパイラは構造体の各要素が自然な配置になるように、構造体を埋めます。 次のコード例の構造体 struct x_
について考えてみます。
struct x_
{
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
char d; // 1 byte
} bar[3];
コンパイラは自然な配置を強制するようにこの構造体を埋めます。
次のコード例は、コンパイラがメモリに埋め込みの構造体を配置する方法を示しています。
// Shows the actual memory layout
struct x_
{
char a; // 1 byte
char _pad0[3]; // padding to put 'b' on 4-byte boundary
int b; // 4 bytes
short c; // 2 bytes
char d; // 1 byte
char _pad1[1]; // padding to make sizeof(x_) multiple of 4
} bar[3];
両方の宣言は、sizeof(struct x_)
を 12 バイトとして返します。
2 番目の宣言には、埋め込みの 2 つの要素が含まれています。
char _pad0[3]
。int b
のメンバーを 4 バイト境界上に配置します。char _pad1[1]
。構造体struct _x bar[3];
の配列要素を 4 バイト境界上に配置します。
埋め込みは自然なアクセスを可能にする方法でbar[3]
の要素を配置します。
bar[3]
配列のレイアウトを次のコード サンプルに示します。
adr offset element
------ -------
0x0000 char a; // bar[0]
0x0001 char pad0[3];
0x0004 int b;
0x0008 short c;
0x000a char d;
0x000b char _pad1[1];
0x000c char a; // bar[1]
0x000d char _pad0[3];
0x0010 int b;
0x0014 short c;
0x0016 char d;
0x0017 char _pad1[1];
0x0018 char a; // bar[2]
0x0019 char _pad0[3];
0x001c int b;
0x0020 short c;
0x0022 char d;
0x0023 char _pad1[1];
alignof
および alignas
alignas
型指定子は、変数とユーザー定義型のカスタムの配置を指定する移植可能な C++ の標準的な方法です。 alignof
演算子も同様に、指定された型または変数の配置を取得する、標準的で移植可能な方法です。
例
クラス、構造体つまり共用体、または個別のメンバーに対して alignas
を使用できます。 複数の alignas
指定子が検出された場合、コンパイラは、最も厳密なもの (最大値を持つもの) を選択します。
// alignas_alignof.cpp
// compile with: cl /EHsc alignas_alignof.cpp
#include <iostream>
struct alignas(16) Bar
{
int i; // 4 bytes
int n; // 4 bytes
alignas(4) char arr[3];
short s; // 2 bytes
};
int main()
{
std::cout << alignof(Bar) << std::endl; // output: 16
}