ソフトウェア プログラマは、メモリを単純なバイト配列と考えます。基本的なデータ型は 1 つまたは複数のメモリ ブロックで構成されます。ただし、コンピューターのプロセッサは、1 バイト サイズのチャンクではメモリの読み出しおよび書き込みができません。現在の CPU は、2、4、8、16、または 32 バイトのチャンクで一度にメモリにアクセスしますが、32 ビットおよび 64 ビットの命令セット アーキテクチャ (ISA) が最も一般的です。システム内でのメモリの分類方法により、これらのチャンクのアドレスはそのサイズの倍数である必要があります。アドレスがこの要件を満たしている場合は、それが「アライメント」されているということです。ハイレベルのプログラマがメモリについてどう考えるかと、最新のプロセッサが実際にメモリでどのように動作するかの違いは、アプリケーションの正確性とパフォーマンスの点で非常に重要です。たとえば、ソフトウェアのアドレス アライメントの問題を理解していない場合、次のような状況になる可能性があります。
- ソフトウェアの動作が遅くなる
- アプリケーションがロックアップまたはハングする
- オペレーティング システムがクラッシュする
- ソフトウェアが通知なくエラーになり、結果が不正確になる
C++ 言語には、さまざまなサイズの基本的な型が用意されています。これらの型の変数を早く操作するため、生成されたオブジェクト コードは、データ型全体を一度に読み書きする CPU 命令を使用しようとします。これは、これらの型の変数を、アドレスが適切にアライメントされるようにメモリに配置する必要があるということです。その結果、各基本型には、サイズのほかに別のプロパティ (アライメント要件) もあります。基本的な型のアライメントがそのサイズと同じように見えるかもしれません。特定の型に最も適した CPU 命令は、一度にデータの一部にしかアクセスできない可能性があるため、一般的にはこれは当てはまりません。たとえば、32 ビットの x86 GNU/Linux マシンは、一度に最大 4 バイトまでしか読み出せないため、64 ビットの long long
型のサイズは 8、アライメントは 4 になります。次の表は、32 ビットおよび 64 ビットの x86-64 GNU/Linux マシンの C/C++ の基本的なネイティブ データ型のサイズとアライメント (バイト) を示しています。
データ型 | 32 ビットの x86 GNU/Linux | 64 ビットの x86 GNU/Linux | ||
---|---|---|---|---|
サイズ | アライメント | サイズ | アライメント | |
bool | 1 | 1 | 1 | 1 |
char | 1 | 1 | 1 | 1 |
short int | 2 | 2 | 2 | 2 |
int | 4 | 4 | 4 | 4 |
long int | 4 | 4 | 8 | 8 |
long long int | 8 | 4 | 8 | 8 |
float | 4 | 4 | 4 | 4 |
double | 8 | 4 | 8 | 8 |
long double | 12 | 4 | 16 | 16 |
void* | 4 | 4 | 8 | 8 |
__attribute__ ((aligned(X)))
言語拡張を提供しています。たとえば、次の宣言では、コンパイラは 16 バイトの境界上にグローバル変数 x を割り当てます。int x __attribute__ ((aligned (16))) = 0;
__attribute__((aligned (X)))
では、適用される変数のサイズは変更されませんが、構造体の要素間にパディングが挿入されることにより、構造体のメモリ レイアウトが変更される場合があります。その結果、構造のサイズが変更されます。aligned 属性にアライメント係数を指定しない場合、コンパイラは自動的に宣言された変数またはフィールドのアライメントを、コンパイル対象のターゲット マシン上の任意のデータ型に使用される最大のアライメントに設定します。この方法でアライメントした変数やフィールドとの間でコピーをすると、コンパイラが最大のメモリ チャンクをコピーする命令を使用できるため、コピー操作の効率が向上することがよくあります。aligned
属性はアライメントを増加させるだけで、減少させることはできません。C++ 関数のoffsetof を使用すると、構造体内の各メンバー要素のアライメントを決定できます。