目次
この記事では主に構造体のメモリアライメントとサイズの計算方法を紹介します。
構造体メモリのアライメントを学ぶ前に、メンバー変数の型と数が同じであるが順序が異なる 2 つの構造体がある場合、それらのサイズを計算するときに、それらのサイズが異なることに気づいたかどうかはわかりません。次のコード:
#include<stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
int i;
char c1;
char c2;
};
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
sizeof によって計算されたサイズは同じではありません。
ここで、構造のサイズを計算する方法を学びましょう。
1. 構造メモリの調整
構造体のメモリ アライメントとは、コンパイラが構造体変数のメモリ空間を割り当てるときに、構造体のアクセス効率とメモリ アライメント要件を確保するために、特定のルールに従って構造体のメンバーを配置することを意味します。
構造体のサイズを計算する前に、構造体のメモリ配置規則を理解する必要があります。
1. 最初のメンバは、構造体変数からのオフセットが 0 のアドレスにあります。
2. その他のメンバ変数は、ある数値(アライメント番号)の整数倍のアドレスにアライメントされている必要があります。Alignment = コンパイラのデフォルトのアライメントとメンバー サイズの小さい値。VS のデフォルト値は 8 です。Linux にはデフォルトのアライメント番号はなく、アライメント番号はメンバー自体のサイズです。
3.構造体の合計サイズは、最大アライメント番号の整数倍です (各メンバー変数にはアライメント番号があります)。
4. 構造がネストされている場合、ネストされた構造はそれ自体の最大アライメントの整数倍にアライメントされ、構造全体のサイズはすべての最大アライメント (ネストされた構造のアライメントを含む) 倍の整数になります。
オフセットの理解:
offsetof を 使用して、構造体の開始位置と比較した構造体のメンバー変数のオフセットを計算します。
#include<stddef.h>
#include<stdio.h>
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
printf("%d\n", offsetof(struct S1, c1));
printf("%d\n", offsetof(struct S1, i));
printf("%d\n", offsetof(struct S1, c2));
return 0;
}
各メンバー変数のオフセットに従って、メモリ内の構造体メンバー変数の格納場所を計算できます。
上記の現象を解析した結果、構造体のメンバーがメモリ上に連続的に格納されていないことが分かりました。
無駄なスペースが存在する理由を知りたければ、構造の配置規則に従って学習を続ける必要があります。
2. 構造物のサイズの計算
以下では、構造体のメモリ アライメント ルールに従って構造体のサイズを計算する方法について説明します。
2.1 構造体では、一般的なデータ型のサイズ計算のみ
または、次の構造タイプを例として取り上げます。
struct S1
{
char c1;
int i;
char c2;
};
同様に、次の構造体のサイズを計算します。
struct S2
{
int i;
char c1;
char c2;
};
この時点で、最初の疑問は解決されました。
学んだ知識を定着させるために、構造のサイズを計算する別の例を次に示します。
struct S3
{
double d;
char c;
int i;
};
構造体に配列が含まれる場合、サイズを計算するにはどうすればよいですか?
次の図に示すように、構造体内に配列型の変数がある場合、その配列を同じ型の複数の変数と見なすだけで済みます。
テストをコンパイルします。
2.2 構造内の入れ子構造のサイズの計算
入れ子構造を含む構造の場合、4 番目のルールを使用する必要があります。
構造がネストされている場合、ネストされた構造はその最大アライメントの整数倍にアライメントされ、構造全体のサイズはすべての最大アライメント (ネストされた構造のアライメントを含む) の整数倍になります。
試行を計算します。
struct S
{
double d1;
char c1;
int i;
};
struct S7
{
char c2;
struct S s;
double d2;
};
int main()
{
printf("%d\n", sizeof(struct S7));
return 0;
}
3. デフォルトのアライメント番号を変更する
#pragma 前処理ディレクティブについては以前に説明しましたが、ここではそれを再度使用してデフォルトの配置を変更します。
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack() //取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
出力結果:
通常の状況では、アライメント番号は 2 の累乗に設定され、任意に他の番号に設定されることはありません。
結論: 構造物の配置が適切でない場合、デフォルトの配置を自分で変更できます。
4. メモリアラインメントはなぜ存在するのでしょうか?
ほとんどの参考文献には次のようなことが書かれています。
1. プラットフォームの理由 (移植の理由):
すべてのハードウェア プラットフォームが任意のアドレスの任意のデータにアクセスできるわけではありません。一部のハードウェア プラットフォームは、特定のアドレスで特定の種類のデータしかフェッチできず、そうでない場合はハードウェア例外がスローされます。
2. パフォーマンス上の理由:
データ構造 (特にスタック) は、できる限り自然な境界上に配置する必要があります。その理由は、アライメントされていないメモリにアクセスするには、プロセッサが 2 回のメモリ アクセスを行う必要があるのに対し、アライメントされたメモリ アクセスには 1 回のアクセスしか必要ないためです。
一般に、構造のメモリの調整は、空間と時間を交換することです。
拡大する:
構造体のメモリ アライメントとは、コンパイラが構造体変数のメモリ空間を割り当てるときに、構造体のアクセス効率とメモリ アライメント要件を確保するために、特定のルールに従って構造体のメンバーを配置することを意味します。
コンピュータでは、メモリにアクセスできる速度は制限されており、通常は特定のバイト サイズによって制限されます。メモリ アクセスの効率を向上させるために、多くのコンピュータ アーキテクチャでは、メモリ内の特定の種類のデータのアドレスが特定の値の倍数である必要があります。この特定の値は通常、データ型のサイズまたはプロセッサのワード サイズです。
構造メモリのアライメントの目的は、これらのアライメント要件を満たしてメモリ アクセスの時間とコストを削減することです。構造体のメンバ変数を整列規則に従って配置すると、各メンバ変数のアドレスの整列が保証され、メモリアクセスの効率が向上します。
正確な位置合わせ規則は、コンパイラ、オペレーティング システム、プロセッサによって異なる場合があります。一般に、位置合わせルールでは、データ型のサイズと位置合わせ要件、および構造体メンバーの順序と型が考慮されます。コンパイラは、構造体のメンバー間にパディング バイト (Padding Bytes) を挿入し、各メンバーのアドレスがアライメント要件を満たしていることを確認します。
パディングバイトが追加のメモリスペースを占有するため、構造のメモリアライメントによって構造のサイズが増加する可能性があることに注意してください。このサイズの増加は、特に構造体、配列、ファイル IO のネストが関係する場合、構造体のメモリ レイアウトとメモリ フットプリントに影響を与える可能性があります。
一部の特殊なケースでは、コンパイラーによって提供される命令または属性を使用して、特定のニーズを満たすために構造体のメモリー・アライメントを制御できます。
構造を設計するときは、位置合わせを満たし、スペースを節約する必要があります。
狭いスペースにいるメンバーはできるだけ集まるようにしましょう。
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
S1 タイプと S2 タイプのメンバーはまったく同じですが、S1 と S2 が占有するスペースのサイズに若干の違いがあります。