web-dev-qa-db-ja.com

Cの構造のパディング

これはインタビューの質問です。今まで、そのような質問は純粋にコンパイラに依存しており、心配する必要はないと思っていましたが、今ではかなり興味があります。

次の2つの構造が与えられているとします。

struct A {  
  int* a;  
  char b;  
 }  

そして、

struct B {  
  char a;  
  int* b;  
}  

あなたはどちらを好むでしょうか、そしてなぜですか?私の答えは次のようになりました(暗闇でやや撮影していました)が、コンパイラはWordサイズのいくつかの倍数(ポインタのサイズ-32ビットマシンと64ビットの8バイト)。したがって、両方の構造に対して、コンパイラは8バイトを割り当てます(32ビットマシンを想定)。しかし、最初のケースでは、すべての変数の後に(つまり、aとbの後に)パディングが行われます。そのため、万が一bがオーバーフローして、次のパディングされたバイトを破壊する値を取得したとしても、aは安全です。

彼はあまり満足していないようで、最初の構造の欠点を2番目の構造よりも求めました。言うことはあまりありませんでした。 :D

答えを教えてください。

40
letsc

この構造のいずれにも利点があるとは思わない。この方程式には1つの(!)定数があります。構造体のメンバーの順序は、宣言どおりであることが保証されています。

したがって、次のような場合、2番目の構造mightには利点があります。おそらくサイズが小さいためですが、例では同じサイズになる可能性があるためです。

struct {
    char a;
    int b;
    char c;
} X;

Vs.

struct {
    char a;
    char b;
    int c;
} Y;

以下のコメントに関するもう少しの説明:

以下はすべて100%ではありませんが、intが32ビットである32ビットシステムで構造体を構築する一般的な方法です。

構造X:

|     |     |     |     |     |     |     |     |     |     |     |     |
 char  pad    pad   pad   ---------int---------- char   pad   pad   pad   = 12 bytes

構造体Y:

|     |     |     |     |     |     |     |     |
 char  char  pad   pad   ---------int----------        = 8 bytes
34
MByD

一部のマシン より効率的にデータにアクセスする 値が境界に整列したとき。いくつかの require 整列するデータ。

SPARCまたはIntel [34] 86、または68020以降のMotorolaチップのような最新の32ビットマシンでは、通常、各データitenは「自己整合」で開始する必要があります。型サイズの倍数であるアドレス上したがって、32ビット型は32ビット境界で始まり、16ビット型は16ビット境界で、8ビットで始まる必要があります型はどこからでも開始できます。struct/array/union型には、最も制限の厳しいメンバーの配置があります。

だからあなたが持つことができる

struct B {  
    char a;
    /* 3 bytes of padding ? More ? */
    int* b;
}

「自己整合」の場合のパディングを最小限に抑える単純なルール(および他のほとんどの場合は害はありません)は、サイズを小さくして構造体メンバーを順序付けることです。

個人的には、2番目の構造と比較した場合、最初の構造の方が不利ではないようです。

11
cnicutar

この特定のケースでは、2番目の構造よりも最初の構造の不利な点を考えることはできませんが、最大のメンバーを最初に置くという一般的な規則に不利な例を見つけることができます。

struct A {  
    int* a;
    short b;
    A(short num) : b(2*num+1), a(new int[b]) {} 
    // OOPS, `b` is used uninitialized, and a good compiler will warn. 
    // The only way to get `b` initialized before `a` is to declare 
    // it first in the class, or of course we could repeat `2*num+1`.
}

CPUがポインター+オフセットにアクセスするための高速アドレッシングモード、小さなオフセット値(たとえば、最大8ビット、または即時値のその他の制限)を持っている大きな構造体の非常に複雑なケースについても聞きました。最速の命令の範囲内でできるだけ多くの最も一般的に使用されるフィールドを配置することにより、大きな構造を最適化することが最適です。

CPUには、pointer + offsetおよびpointer + 4 * offsetの高速アドレッシングさえあります。次に、64個のcharフィールドと64個のintフィールドがあると仮定します。charフィールドを最初に配置すると、両方のタイプのすべてのフィールドに最適な指示を使用してアドレス指定できます。 -alignedは、256バイトの制限を超えているため、イミディエート値ではなくレジスタに定数をロードするなどして、異なる方法でアクセスする必要があります。

自分でやる必要はありませんでした。たとえば、x86はとにかく大きな即値を許可します。 Assemblyをじっと見つめる時間をかけない限り、だれも通常考える最適化のようなものではありません。

4
Steve Jessop

簡単に言えば、どちらも一般的な場合を選択しても利点はありません。実際に選択が重要になる唯一の状況は、構造パッキングが有効の場合です。struct Aがより良い選択です(メモリ内で両方のフィールドが整列されるため、struct Bbフィールドは奇数オフセットに配置されます)。構造パッキングとは、構造内にパディングバイトが挿入されないことを意味します。

ただし、これはかなり一般的ではないシナリオです。通常、構造体のパッキングは特定の状況でのみ有効になります。ほとんどのプログラムでは問題ありません。また、C標準の移植可能な構造を介して制御することもできません。

2
alecov

これも推測に値しますが、ほとんどのコンパイラには、パディングバイトを明示的に追加しない不整合オプションがあります。これには、(一部のプラットフォームで)実行時の修正(ハードウェアトラップ)が必要であり、その場でアクセスを調整します(対応するパフォーマンスペナルティを伴います)。記憶が正しければ、HPUXはこのカテゴリに分類されます。そのため、コンパイラオプションの位置合わせが間違っていても、フィールドの最初の構造体はまだ位置合わせされています(パディングが最後にあると言ったため)。

1
Kevin