sizeof
構造体のメンバーの合計サイズよりも構造体に対して大きいサイズを返すのはなぜですか?
sizeof
構造体のメンバーの合計サイズよりも構造体に対して大きいサイズを返すのはなぜですか?
回答:
これは、位置合わせの制約を満たすためにパディングが追加されたためです。データ構造の整列は、プログラムのパフォーマンスと正確さの両方に影響を与えます。
SIGBUS
)。以下は、x86プロセッサの一般的な設定を使用した例です(すべて32ビットおよび64ビットモードを使用)。
struct X
{
short s; /* 2 bytes */
/* 2 padding bytes */
int i; /* 4 bytes */
char c; /* 1 byte */
/* 3 padding bytes */
};
struct Y
{
int i; /* 4 bytes */
char c; /* 1 byte */
/* 1 padding byte */
short s; /* 2 bytes */
};
struct Z
{
int i; /* 4 bytes */
short s; /* 2 bytes */
char c; /* 1 byte */
/* 1 padding byte */
};
const int sizeX = sizeof(struct X); /* = 12 */
const int sizeY = sizeof(struct Y); /* = 8 */
const int sizeZ = sizeof(struct Z); /* = 8 */
(Z
上記の例の構造のように)配置によってメンバーを並べ替えることで、構造のサイズを最小化できます(基本タイプではサイズで並べ替えれば十分です)。
重要な注意:CとC ++の両方の標準では、構造の配置は実装定義であると述べています。したがって、各コンパイラーはデータを異なる方法で配置することを選択する可能性があり、その結果、データ・レイアウトが異なり、互換性がなくなります。このため、異なるコンパイラーで使用されるライブラリーを扱う場合、コンパイラーがデータを調整する方法を理解することが重要です。一部のコンパイラには、コマンドライン設定や#pragma
構造体配置設定を変更するための特別なステートメントがあります。
ここのC FAQ で説明されているパッキングとバイトアライメント:
アライメント用です。多くのプロセッサは、あらゆる方法で詰め込まれている場合、2バイトと4バイトの量(intやlong intなど)にアクセスできません。
次の構造があるとします。
struct { char a[3]; short int b; long int c; char d[3]; };
さて、あなたはこの構造をこのようにメモリに詰め込むことが可能であるべきだと思うかもしれません:
+-------+-------+-------+-------+ | a | b | +-------+-------+-------+-------+ | b | c | +-------+-------+-------+-------+ | c | d | +-------+-------+-------+-------+
しかし、コンパイラーが次のように配置すると、プロセッサー上ではるかに簡単になります。
+-------+-------+-------+ | a | +-------+-------+-------+ | b | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | +-------+-------+-------+
パックバージョンでは、bフィールドとcフィールドがどのように折り返されるかを確認するのが、あなたと私にとって少なくとも少し難しいことに気づきましたか?一言で言えば、プロセッサにとっても難しいです。したがって、ほとんどのコンパイラーは、次のように(余分な非表示フィールドがあるかのように)構造にパディングします。
+-------+-------+-------+-------+ | a | pad1 | +-------+-------+-------+-------+ | b | pad2 | +-------+-------+-------+-------+ | c | +-------+-------+-------+-------+ | d | pad3 | +-------+-------+-------+-------+
s
then &s.a == &s
および&s.d == &s + 12
(呼び出された位置合わせが与えられた場合)呼び出された場合。ポインタが格納されるのは、配列のサイズが可変である(たとえば、の代わりa
に宣言された)場合のみですが、要素は別の場所に格納する必要があります。char a[]
char a[3]
たとえばGCCで特定のサイズの構造にしたい場合は、を使用します__attribute__((packed))
。
Windowsでは、/ Zpオプションを指定してcl.exeコンパイラを使用するときに、配置を1バイトに設定できます。
通常、プラットフォームやコンパイラによっては、CPUが4(または8)の倍数のデータにアクセスする方が簡単です。
したがって、基本的には調整の問題です。
それを変更するには、十分な理由が必要です。
これは、バイトアライメントとパディングが原因で、プラットフォーム上で構造体が偶数バイト(またはワード)になることが原因である可能性があります。たとえば、LinuxのCでは、次の3つの構造があります。
#include "stdio.h"
struct oneInt {
int x;
};
struct twoInts {
int x;
int y;
};
struct someBits {
int x:2;
int y:6;
};
int main (int argc, char** argv) {
printf("oneInt=%zu\n",sizeof(struct oneInt));
printf("twoInts=%zu\n",sizeof(struct twoInts));
printf("someBits=%zu\n",sizeof(struct someBits));
return 0;
}
サイズ(バイト単位)がそれぞれ4バイト(32ビット)、8バイト(2x 32ビット)、1バイト(2 + 6ビット)であるメンバーがいる。上記のプログラム(Linuxでgccを使用)は、サイズを4、8、および4として出力します。最後の構造は、1ワード(32ビットプラットフォームでは4 x 8ビットバイト)になるように埋め込まれます。
oneInt=4
twoInts=8
someBits=4
以下も参照してください。
Microsoft Visual Cの場合:
http://msdn.microsoft.com/en-us/library/2e70t5y1%28v=vs.80%29.aspx
GCCは、Microsoftのコンパイラとの互換性を主張しています。
http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html
以前の回答に加えて、パッケージに関係なく、C ++にはメンバー注文保証はありません。コンパイラーは、仮想テーブルポインターと基本構造体のメンバーを構造体に追加する場合があります(もちろん、追加します)。標準では仮想テーブルの存在さえ保証されていないため(仮想メカニズムの実装は指定されていません)、そのため、そのような保証は不可能であると結論付けることができます。
メンバーの順序が Cで保証されていると確信していますが、クロスプラットフォームまたはクロスコンパイラーのプログラムを作成するときは、それを当てにしません。
構造体のサイズは、いわゆるパッキングのため、パーツの合計よりも大きくなります。特定のプロセッサには、それが動作する優先データサイズがあります。32ビット(4バイト)の場合、最新のプロセッサーの推奨サイズ。データがこの種類の境界にある場合のメモリへのアクセスは、そのサイズの境界をまたぐものよりも効率的です。
例えば。単純な構造を考えてみましょう:
struct myStruct
{
int a;
char b;
int c;
} data;
マシンが32ビットマシンであり、データが32ビットの境界に配置されている場合、当面の問題が発生します(構造体のアライメントがない場合)。この例では、構造体データがアドレス1024から始まると仮定します(0x400-最下位の2ビットはゼロなので、データは32ビット境界に揃えられます)。data.aへのアクセスは、0x400の境界で始まるため、正常に機能します。data.bへのアクセスもアドレス0x404(別の32ビット境界)にあるため、正常に機能します。しかし、アラインされていない構造では、data.cがアドレス0x405に配置されます。data.cの4バイトは0x405、0x406、0x407、0x408にあります。32ビットマシンでは、システムは1メモリサイクル中にdata.cを読み取りますが、4バイトのうち3バイトしか取得しません(4番目のバイトは次の境界にあります)。したがって、システムは4番目のバイトを取得するために2回目のメモリアクセスを行う必要があります。
ここで、data.cをアドレス0x405に配置する代わりに、コンパイラーが構造体に3バイトを埋め込み、data.cをアドレス0x408に配置すると、システムはデータを読み取るのに1サイクルだけで済み、そのデータ要素へのアクセス時間が短縮されます。 50%。パディングは、メモリ効率と処理効率を交換します。コンピューターに大量のメモリ(数ギガバイト)が搭載されている場合、コンパイラーはスワップ(サイズ対速度)が妥当なものであると感じます。
残念ながら、ネットワークを介して構造体を送信したり、バイナリデータをバイナリファイルに書き込んだりすると、この問題が深刻になります。構造体またはクラスの要素の間に挿入されたパディングは、ファイルまたはネットワークに送信されるデータを破壊する可能性があります。移植可能なコード(いくつかの異なるコンパイラーを使用するコード)を作成するには、おそらく適切な「パッキング」を保証するために、構造の各要素に個別にアクセスする必要があります。
一方、コンパイラーによって、データ構造のパッキングを管理する機能が異なります。たとえば、Visual C / C ++では、コンパイラは#pragma packコマンドをサポートしています。これにより、データのパッキングと配置を調整できます。
例えば:
#pragma pack 1
struct MyStruct
{
int a;
char b;
int c;
short d;
} myData;
I = sizeof(myData);
これで、長さは11になります。プラグマなしでは、コンパイラーのデフォルトのパッキングに応じて、11から14(およびシステムによっては32まで)になる可能性があります。
#pragma pack
。メンバーがデフォルトの配置に割り当てられている場合、一般的に構造はパックされていないと思います。
構造体の配置を暗黙的または明示的に設定している場合は、そうすることができます。メンバーのサイズが4バイトの倍数ではない場合でも、4に整列された構造体は常に4バイトの倍数になります。
また、ライブラリはx86で32ビットのintを使用してコンパイルでき、64ビットのプロセスでそのコンポーネントを比較すると、手動でこれを行うと異なる結果が得られます。
C99 N1256標準ドラフト
http://www.open-std.org/JTC1/SC22/WG14/www/docs/n1256.pdf
6.5.3.4 sizeof演算子:
3構造型または共用体型のオペランドに適用した場合、結果は、内部および末尾の埋め込みを含む、そのようなオブジェクトの合計バイト数になります。
6.7.2.1構造体と共用体の指定子:
13 ...構造オブジェクト内に名前のないパディングが存在する可能性がありますが、その最初にはありません。
そして:
15構造体または共用体の最後に名前のないパディングがある場合があります。
新しいC99 フレキシブル配列メンバー機能(struct S {int is[];};
)もパディングに影響を与える可能性があります。
16特別な場合として、複数の名前付きメンバーを持つ構造体の最後の要素は、不完全な配列型になる場合があります。これは、フレキシブルアレイメンバーと呼ばれます。ほとんどの場合、フレキシブル配列メンバーは無視されます。特に、構造のサイズは、省略された場合よりも末尾のパディングが多い場合があることを除いて、フレキシブルアレイメンバーが省略されたかのようになります。
附属書Jの移植性の問題は、繰り返し述べます。
以下は不特定です:...
- 構造体または共用体に値を格納するときの埋め込みバイトの値(6.2.6.1)
C ++ 11 N3337標準ドラフト
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3337.pdf
5.3.3サイズ:
2クラスに適用した場合、結果は、そのクラスのオブジェクトのバイト数であり、そのタイプのオブジェクトを配列に配置するために必要なパディングを含みます。
9.2クラスのメンバー:
relaypret_castを使用して適切に変換された標準レイアウト構造体オブジェクトへのポインターは、その初期メンバー(またはそのメンバーがビットフィールドの場合は、それが存在するユニットを指す)を指し、その逆も同様です。[注:したがって、適切な配置を実現するために必要に応じて、標準レイアウトの構造体オブジェクト内に名前のないパディングが存在する可能性がありますが、最初はパディングされていません。—エンドノート]
注を理解するのに十分なC ++しか知りません:-)
他の回答に加えて、構造体は仮想関数を持つことができますが(通常は持たない)、その場合、構造体のサイズにはvtblのスペースも含まれます。
C言語では、メモリ内の構造要素の場所についてコンパイラにある程度の自由を与えます。
C言語は、プログラマーに構造内の要素のレイアウトを保証します。
要素の配置に関連する問題:
配置の仕組み:
psより詳細な情報はこちらから入手できます: "Samuel P.Harbison、Guy L.Steele CA Reference、(5.6.2-5.6.7)"
アイデアは、速度とキャッシュを考慮して、オペランドは自然なサイズに調整されたアドレスから読み取る必要があるということです。これを実現するために、コンパイラーは構造体メンバーをパディングして、次のメンバーまたは構造体が整列されるようにします。
struct pixel {
unsigned char red; // 0
unsigned char green; // 1
unsigned int alpha; // 4 (gotta skip to an aligned offset)
unsigned char blue; // 8 (then skip 9 10 11)
};
// next offset: 12
x86アーキテクチャは、常に不整合なアドレスをフェッチすることができました。ただし、速度は遅く、ミスアライメントが2つの異なるキャッシュラインに重なる場合、アラインされたアクセスが1つしか排除しないときに、2つのキャッシュラインが排除されます。
一部のアーキテクチャは実際には、正しく調整されていない読み取りと書き込み、およびARMアーキテクチャの初期バージョン(今日のすべてのモバイルCPUに進化したもの)をトラップする必要があります。(下位ビットは無視されました。)
最後に、キャッシュラインは任意に大きくなる可能性があり、コンパイラはそれらを推測したり、スペースと速度のトレードオフを試みたりしないことに注意してください。代わりに、アラインメントの決定はABIの一部であり、最終的にキャッシュラインを均等に埋める最小のアラインメントを表します。
TL; DR:アラインメントは重要です。