提供された回答については、実際のアライメントとは何かについて混乱があるようです。アラインメントには 2 種類あるため、おそらく混乱が生じるでしょう。
1. メンバーの調整
これは、構造体/クラス型内のメンバーの特定の順序について、インスタンスがバイト数でどのくらい大きいかを示す定性的な尺度です。一般に、コンパイラは、メンバーが構造内でバイト サイズの降順 (つまり、最初に最大、最小のメンバーが最後) に並べられている場合、構造/クラス インスタンスを圧縮できます。検討:
struct A
{
char c; float f; short s;
};
struct B
{
float f; short s; char c;
};
どちらの構造にもまったく同じ情報が含まれています。この例のために; float 型は 4 バイト、short 型は 2 バイト、文字は 1 バイトです。ただし、最初の構造体 A はランダムな順序でメンバーを持ち、2 番目の構造体 B はバイト サイズに従ってメンバーを並べ替えます (これは特定のアーキテクチャでは異なる場合があります。この例では 4 バイト アラインメントの x86 Intel CPU アーキテクチャを想定しています)。次に、構造体のサイズを検討してください。
printf("size of A: %d", sizeof (A)); // size of A: 12;
printf("size of B: %d", sizeof (B)); // size of B: 8;
サイズが 7 バイトであると予想される場合は、メンバーが1 バイトのアラインメントを使用して構造体にパックされていると想定することになります。これを許可するコンパイラもありますが、一般に、ほとんどのコンパイラは歴史的な理由から 4 バイトまたは 8 バイトのアラインメントを使用します (ほとんどの CPU は DWORD (ダブルワード) または QWORD (クワッドワード) 汎用レジスタで動作します)。
パッキングを実現するために、2 つのパディング メカニズムが機能しています。
最初に、結果のバイト サイズがバイト アラインメントより小さいか等しい場合、バイト アラインメントより小さいバイト サイズを持つ各メンバーは、次のメンバーと「マージ」されます。構造体 B では、メンバー s と c をこの方法でマージできます。それらを合わせたサイズは、s の場合は 2 バイト + c の場合は 1 バイト == 3 バイト <= 4 バイトのアラインメントです。構造体 A の場合、このようなマージは発生せず、各メンバーは構造体のパッキングで実質的に 4 バイトを消費します。
次の構造体がアラインメント境界で開始できるように、構造体の合計サイズが再度パディングされます。例 B では、合計バイト数は 7 になります。次の 4 バイト境界はバイト 8 にあるため、構造体は 1 バイトでパディングされ、インスタンスのタイトなシーケンスとして配列割り当てが可能になります。
Visual C++ / GCC では、1 バイト、2 バイト、およびそれ以上の 2 バイトの倍数の異なるアラインメントが許可されていることに注意してください。これは、アーキテクチャーに最適なコードを生成するコンパイラーの能力に反することを理解してください。実際、次の例では、読み取り操作ごとに 1 バイト命令を使用して、各バイトが 1 バイトとして読み取られます。実際には、4 バイトが同じ DWORD にあり、1 つの命令で CPU レジスタにロードできる場合でも、ハードウェアはキャッシュに読み取られた各バイトを含むメモリ ライン全体をフェッチし、命令を 4 回実行します。
#pragma pack(push,1)
struct Bad
{
char a,b,c,d;
};
#pragma pack(pop)
2. 割り当ての調整
これは、前のセクションで説明した 2 番目のパディング メカニズムと密接に関連していますが、割り当てアラインメントは、 std::aligned_alloc()などのmalloc() / memalloc()割り当て関数のバリアントで指定できます。したがって、構造体/オブジェクト型のバイト アラインメントが示唆するものとは異なる (通常は 2 の倍数より大きい) アラインメント境界でオブジェクトを割り当てることができます。
size_t blockAlignment = 4*1024; // 4K page block alignment
void* block = std::aligned_alloc(blockAlignment, sizeof(T) * count);
このコードは、タイプ T の count インスタンスのブロックを、4096 の倍数で終わるアドレスに配置します。
このような割り当てアラインメントを使用する理由も、純粋にアーキテクチャ上のものです。たとえば、アドレスの範囲がキャッシュ レイヤーにうまく収まるため、ページ アラインされたアドレスからのブロックの読み取りと書き込みが高速になります。異なる「ページ」に分割された範囲は、ページの境界を越えるときにキャッシュを破棄します。メディア (バス アーキテクチャ) が異なれば、アクセス パターンも異なります。一般に、4、16、32、および 64 K ページ サイズのアラインメントは珍しくありません。
言語バージョンとプラットフォームは、通常、このような整列された割り当て関数の特定のバリアントを提供することに注意してください。たとえば、Unix/Linux 互換のposix_memalign()関数は、ptr 引数によってメモリを返し、失敗した場合はゼロ以外のエラー値を返します。
- int posix_memalign(void **memptr, size_t アライメント, size_t サイズ); // POSIX(Linux/UX)
- void *aligned_alloc( size_t アライメント, size_t サイズ ); // C++11
- void *std::aligned_alloc( size_t アライメント, size_t サイズ ); // c++17
- void *aligned_malloc( size_t サイズ, size_t アライメント ); MicrosoftVS2019