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


697

sizeof構造体のメンバーの合計サイズよりも構造体に対して大きいサイズを返すのはなぜですか?


14
メモリー調整については、このC FAQを参照してください。c-faq.com/struct/align.esr.html
Richard Chambers

48
逸話:ホストプログラムの構造体パディング内にコードを配置する実際のコンピューターウイルスがありました。
Elazar 2013

4
@エラザーそれは印象的です!私はそのような小さな領域を何にでも使用することが可能だとは思っていませんでした。詳細を教えていただけますか?
Wilson、

1
@ウィルソン-私はそれがたくさんのjmpを含んでいると確信しています。
hoodaticus 2017年

4
構造のパディング、パッキングを参照してください: Cの失われた芸術の構造パッキングエリックS.レイモンド
EsmaeelE 2017

回答:


649

これは、位置合わせの制約を満たすためにパディングが追加されたためです。データ構造の整列は、プログラムのパフォーマンスと正確さの両方に影響を与えます。

  • 不揃いのアクセスはハードエラーの場合があります(多くの場合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構造体配置設定を変更するための特別なステートメントがあります。


38
ここで書き留めておきたいのは、ほとんどのプロセッサは(前述のように)アライメントされていないメモリアクセスに対してペナルティを課しますが、多くのプロセッサが完全に許可しないことを忘れないでください。特にほとんどのMIPSチップは、非境界整列アクセスで例外をスローします。
Cody Brocious

35
x86チップは、ペナルティが課されますが、非整列アクセスを許可するという点で、実際にはかなりユニークです。AFAIKのほとんどのチップは、いくつかだけではなく例外をスローします。PowerPCも一般的な例です。
Dark Shikari

6
アライメントされていないアクセスのプラグマを有効にすると、通常、ミスアライメントフォールトをスローするプロセッサでコードのサイズが膨らみます。すべてのミスアライメントを修正するコードを生成する必要があるためです。ARMはミスアライメントフォールトもスローします。
マイクディミック

5
@ダーク-完全に同意します。しかし、ほとんどのデスクトッププロセッサはx86 / x64なので、ほとんどのチップはデータアライメントエラーを発行しません;)
Aaron

27
非境界整列データアクセスは通常CISCアーキテクチャにある機能であり、ほとんどのRISCアーキテクチャには含まれていません(ARM、MIPS、PowerPC、Cell)。実際には、ほとんどのチップはデスクトッププロセッサではありません。チップ数による組み込みルールのため、これらの大部分はRISCアーキテクチャです。
Lara Dougan、

191

ここの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  |
+-------+-------+-------+-------+

1
次に、メモリスロットpad1、pad2、およびpad3の使用について説明します。
ラクシュミSreekanth Chitla 16


@EmmEffこれは間違っている可能性がありますが、完全にはわかりません。なぜ配列にポインタ用のメモリスロットがないのですか。
バラージュBörcsök

1
@BalázsBörcsökこれらは定数サイズの配列なので、それらの要素は固定オフセットで構造体に直接格納されます。コンパイラーはコンパイル時にこれをすべて知っているため、ポインターは暗黙的です。たとえば、このタイプの構造体変数がsthen &s.a == &sおよび&s.d == &s + 12(呼び出された位置合わせが与えられた場合)呼び出された場合。ポインタが格納されるのは、配列のサイズが可変である(たとえば、の代わりaに宣言された)場合のみですが、要素は別の場所に格納する必要があります。char a[]char a[3]
kbolino

27

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

Windowsでは、/ Zpオプションを指定してcl.exeコンパイラを使用するときに、配置を1バイトに設定できます

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

したがって、基本的には調整の問題です。

それを変更するには、十分な理由が必要です。


5
「正当な理由」例:32ビットシステムと64ビットシステムの間でバイナリ互換性(パディング)の一貫性を保つために、明日公開される概念実証デモコードの複雑な構造体。時には、必要性が妥当性よりも優先されなければならない。
Mr.Ree、2008

2
オペレーティングシステムについて言及する場合を除き、すべて問題ありません。これはCPU速度の問題であり、OSはまったく関与していません。
Blaisorblade、2009年

3
もう1つの正当な理由は、たとえばネットワークプロトコルを解析するときに、データストリームを構造体に詰め込む場合です。
CEO、

1
@dolmen OSがデータにアクセスしないため、「Operatinシステムの方がデータにアクセスしやすい」というのは正しくないことを指摘しました。
Blaisorblade 2013

1
@dolmen実際、ABI(アプリケーションバイナリインターフェース)について話す必要があります。デフォルトの配置(ソースで変更しない場合に使用)はABIに依存し、多くのOSは複数のABI(たとえば、32ビットと64ビット、または異なるOSのバイナリ、または異なるコンパイル方法)をサポートしています同じOSの同じバイナリ)。OTOH、パフォーマンスの点でどのアライメントが便利かはCPUに依存します。メモリは、32ビットモードと64ビットモードのどちらを使用しても同じ方法でアクセスされます(リアルモードについてはコメントできませんが、最近のパフォーマンスにはほとんど関係がないようです)。IIRC Pentiumは、8バイトのアライメントを優先し始めました。
Blaisorblade 2013

15

これは、バイトアライメントとパディングが原因で、プラットフォーム上で構造体が偶数バイト(またはワード)になることが原因である可能性があります。たとえば、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

4
「gccを使用するLinuxでのC」では、プラットフォームを説明するには不十分です。アラインメントは主にCPUアーキテクチャに依存します。
ドルメン

-@ Kyle Burton すみません、構造体 "someBits"のサイズが4に等しい理由がわかりません。2つの整数が宣言されているため(2 * sizeof(int))= 8バイトなので、8バイトが必要です。ありがとう
youpilat13 2018

1
こんにちは@ youpilat13 :2:6は実際には2ビットと6ビットを指定していますが、この場合は完全な32ビット整数ではありません。someBits.xは2ビットだけなので、4つの可能な値(00、01、10、11(1、2、3、4))しか保存できません。これは理にかなっていますか?この機能に関する記事は次のとおり
カイルバートン

11

以下も参照してください。

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保証されていると確信していますが、クロスプラットフォームまたはクロスコンパイラーのプログラムを作成するときは、それを当てにしません。


4
「メンバーの注文がCでうんざりしていると確信しています」。はい、C99は次のように述べています。「構造オブジェクト内では、非ビットフィールドメンバーとビットフィールドが存在するユニットには、宣言された順に増加するアドレスがあります。」標準的な
利点


8

構造体のサイズは、いわゆるパッキングのため、パーツの合計よりも大きくなります。特定のプロセッサには、それが動作する優先データサイズがあります。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まで)になる可能性があります。


これは構造パディングの結果について説明しますが、質問には答えません。
Keith Thompson

" ...パッキングと呼ばれるものがあるため。...-"パディング "を意味すると思います。" 32ビット(4バイト)の場合、最新のプロセッサの推奨サイズ "-これは少し単純化しすぎています。通常は多くの場合、各サイズは独自のアライメントを持っていると私は確信してあなたの答えは受け入れ答えになっていないのです新しい情報を追加していないよ。。。8、16、32、および64ビットがサポートされているの大きさ
キース・トンプソン

1
パッキングと言ったとき、コンパイラがデータを構造体にパックする方法を意味しました(小さなアイテムにパディングすることでパッキングできますが、パディングする必要はありませんが、常にパックします)。サイズについては、システムがデータアクセスをサポートするものではなく、システムアーキテクチャについて話していました(これは、基礎となるバスアーキテクチャとはかなり異なります)。最後のコメントについては、トレードオフの1つの側面(速度とサイズ)、つまり主要なプログラミングの問題について、簡略化して拡張した説明をしました。また、問題を修正する方法についても説明します-受け入れられた回答にはありませんでした。
sid1138

このコンテキストでの「パッキング」とは、通常、と同様に、デフォルトよりもメンバーをより厳密に割り当てることを指します#pragma pack。メンバーがデフォルトの配置に割り当てられている場合、一般的に構造はパックされていないと思います。
キーストンプソン

パッキングは一種の過負荷用語です。これは、構造要素をメモリに配置する方法を意味します。オブジェクトをボックスに入れる意味と似ています(移動のためのパッキング)。また、パディングなしで要素をメモリに配置することも意味します(「密にパック」するための短い手)。次に、#pragma packコマンドに単語のコマンドバージョンがあります。
sid1138

5

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

また、ライブラリはx86で32ビットのintを使用してコンパイルでき、64ビットのプロセスでそのコンポーネントを比較すると、手動でこれを行うと異なる結果が得られます。


5

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 ++しか知りません:-)


4

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


8
結構です。一般的な実装では、構造体に追加されるのはvtable ポインターです。
Don Wakefield、

3

C言語では、メモリ内の構造要素の場所についてコンパイラにある程度の自由を与えます。

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

C言語は、プログラマーに構造内の要素のレイアウトを保証します。

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

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

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

配置の仕組み:

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

psより詳細な情報はこちらから入手できます: "Samuel P.Harbison、Guy L.Steele CA Reference、(5.6.2-5.6.7)"


2

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.