web-dev-qa-db-ja.com

構造体のsizeofが各メンバのsizeofの合計と等しくないのはなぜですか?

sizeof演算子が構造体のメンバの合計サイズよりも大きいサイズを構造体に返すのはなぜですか?

619
Kevin

これは、配置制約を満たすためにパディングが追加されたためです。 データ構造のアライメント は、プログラムのパフォーマンスと正確性の両方に影響を与えます。

  • アクセスが揃っていないと、ハードエラーになることがあります(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ステートメントがあります。

600
Kevin

C FAQ に記載されているように、パッキングとバイトアライメント

調整用です。多くのプロセッサは、あらゆる方法で詰め込まれている場合、2バイトおよび4バイトの量(たとえば、整数および長整数)にアクセスできません。

この構造があるとします。

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  |
+-------+-------+-------+-------+
162
EmmEff

たとえばGCCで構造体を一定のサイズにしたい場合は、 __attribute__((packed)) を使用します。

Windowsでは、cl.exeコンパイラを / Zpオプション と一緒に使用すると、アライメントを1バイトに設定できます。

通常は、プラットフォームやコンパイラによっては、CPUが4(または8)の倍数のデータにアクセスする方が簡単です。

それで、基本的に整列の問題です。

変更するには正当な理由が必要です。

23
INS

これは、バイトアラインメントとパディングが原因で、構造がプラットフォーム上の偶数バイト(またはワード)になるようになることがあります。たとえば、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バイト(2×32ビット)、1バイト(2 + 6ビット)のメンバーがいます。上記のプログラム(Linux上でgccを使用)は、サイズを4、8、および4として出力します。最後の構造は、1ワード(32ビットプラットフォームでは4 x 8ビットバイト)になるように埋め込まれます。

oneInt=4
twoInts=8
someBits=4
13
Kyle Burton

また見なさい:

microsoft Visual Cの場合:

http://msdn.Microsoft.com/ja-jp/library/2e70t5y1%28v=vs.80%29.aspx

gCCはMicrosoftのコンパイラとの互換性を主張しています。

http://gcc.gnu.org/onlinedocs/gcc/Structure_002dPacking-Pragmas.html

上記の回答に加えて、パッケージに関係なく、 C++ にはmembers-order-ギャランティはありません。コンパイラは、仮想テーブルポインタと基本構造体のメンバを構造体に追加することができます(確かに追加します)。仮想テーブルの存在さえも標準によって保証されていない(仮想メカニズムの実装は指定されていない)ので、そのような保証は単に不可能であると結論付けることができます。

私は member-orderがC 保証されていると確信していますが、クロスプラットフォームを書くときにはそれを当てにしません。クロスコンパイラプログラム。

9
lkanab

構造のサイズは、いわゆるパッキングのために、その部分の合計よりも大きくなります。特定のプロセッサには、それが機能する優先データサイズがあります。最近のほとんどのプロセッサは、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まで)になることもあります。

6
sid1138

暗黙的または明示的に構造体の配置を設定している場合にも可能です。たとえそのメンバのサイズが4バイトの倍数ではない何かであっても、4で整列された構造体は常に4バイトの倍数になります。

また、ライブラリはx86の下で32ビット整数でコンパイルされるかもしれません、そして、あなたが64ビットプロセスでそのコンポーネントを比較しているならあなたがこれを手でやっているならあなたに異なる結果を与えるでしょう。

5
Orion Adrian

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 Sizeof

2クラスに適用された場合、結果はそのクラスのオブジェクトのバイト数になります。これには、そのタイプのオブジェクトを配列に配置するために必要なパディングが含まれます。

9.2クラスメンバー

Reinterpret_castを使用して適切に変換された標準レイアウト構造体オブジェクトへのポインタは、その最初のメンバ(またはそのメンバがビットフィールドの場合はそれが存在するユニット)を指し、その逆も同様です。 [注意:したがって、標準レイアウトのstructオブジェクト内には名前のないパディングがあるかもしれませんが、適切な位置合わせを達成するために必要に応じて、最初はそうではありません。 - エンドノート]

私はメモを理解するのに十分なC++しか知りません:-)

他の答えに加えて、構造体は仮想関数を持つことができます(しかし通常は持たない)。その場合、構造体のサイズはvtblのためのスペースも含みます。

4
JohnMcG

C言語では、コンパイラはメモリ内の構造要素の位置についてある程度自由になります。

  • メモリホールは、任意の2つのコンポーネント間、および最後のコンポーネントの後に表示されることがあります。これは、ターゲットコンピュータ上の特定の種類のオブジェクトが、アドレス指定の境界によって制限される可能性があるためです。
  • sizeof演算子の結果に含まれる「メモリホール」のサイズ。 sizeofには、C/C++で利用可能なフレキシブル配列のサイズだけが含まれていません。
  • この言語のいくつかの実装では、プラグマおよびコンパイラオプションを介して構造体のメモリレイアウトを制御できます。

C言語は、構造内の要素レイアウトについてプログラマにある程度の保証を提供します。

  • メモリアドレスを増やす一連のコンポーネントを割り当てる必要があるコンパイラ
  • 最初のコンポーネントのアドレスが構造体の開始アドレスと一致している
  • 名前のないビットフィールドは、隣接する要素の必要なアドレスアライメントに合わせて構造体に含めることができます。

要素の配置に関する問題:

  • さまざまなコンピュータがさまざまな方法でオブジェクトの端を揃えます
  • ビットフィールドの幅に対するさまざまな制限
  • バイトはWordに格納する方法がコンピュータによって異なります(Intel 80x86およびMotorola 68000)

配置の仕組み:

  • 構造によって占められる容積は、そのような構造の配列の整列した単一要素のサイズとして計算されます。次の構造の最初の要素が境界整列の違反要件にならないように、構造は終了する必要があります

p.sより詳しい情報はこちらから入手できます。

3
bruziuz

考え方は、速度とキャッシュを考慮すると、オペランドは元のサイズに合わせてアドレスから読み取る必要があるということです。これを実現するために、コンパイラは構造体メンバをパディングして、後続のメンバまたは後続の構造体が整列されるようにします。

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:アラインメントは重要です。

2
DigitalRoss