web-dev-qa-db-ja.com

私の本のビットフィールドを持つ構造体の不正確なサイズ

私はC言語の基礎を勉強しています。ビットフィールドを持つ構造の章にたどり着きました。この本は、2つの異なるタイプのデータを持つ構造体の例を示しています。さまざまなブールとさまざまな符号なし整数です。

この本は、構造のサイズが16ビットであり、パディングを使用しないと構造が10ビットであると宣言しています。

これは、例で本が使用する構造です。

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

int main(void)
{
       struct test Test;

       printf("%zu\n", sizeof(Test));

       return 0;
}

私のコンパイラでは、代わりにまったく同じ構造が16ビットbytes(ビットではなく)パディングとパディングなしの16バイトを測定するのはなぜですか?

私は使っています

GCC (tdm-1) 4.9.2 compiler;
Code::Blocks as IDE.
Windows 7 64 Bit
Intel CPU 64 bit

これは私が得ている結果です:

enter image description here

以下に例を示すページの写真を示します。

enter image description here

23
AlexQualcosa

Microsoft ABIは、GCCが通常他のプラットフォームで行うのとは異なる方法でビットフィールドをレイアウトします。 -mms-bitfields オプションでMicrosoft互換レイアウトを使用するか、-mno-ms-bitfieldsで無効にするかを選択できます。 GCCバージョンはデフォルトで-mms-bitfieldsを使用している可能性があります。

ドキュメントによると、-mms-bitfieldsが有効な場合:

  • すべてのデータオブジェクトには位置合わせの要件があります。構造体、共用体、および配列を除くすべてのデータのアライメント要件は、オブジェクトのサイズまたは現在のパッキングサイズ(aligned属性またはpackプラグマで指定)のいずれか小さい方です。構造体、共用体、および配列の場合、整列要件はそのメンバーの最大の整列要件です。すべてのオブジェクトには、次のようにオフセットが割り当てられます。offset%alignment_requirement == 0
  • 整数型が同じサイズであり、次のビットフィールドが共通の境界を超えずに現在の割り当て単位に収まる場合、隣接するビットフィールドは同じ1、2、または4バイトの割り当て単位にパックされます。ビットフィールドのアライメント要件。

boolunsigned intのサイズが異なるため、それらは別々にパックされて整列され、構造体のサイズが大幅に増加します。 unsigned intのアラインメントは4バイトであり、構造体の中央で3回再アラインメントする必要があるため、合計サイズは16バイトになります。

boolunsigned intに変更するか、-mno-ms-bitfieldsを指定することにより、本の同じ動作を得ることができます(ただし、Microsoftコンパイラーでコンパイルされたコードと相互運用できないことを意味します) )。

C標準では、ビットフィールドのレイアウト方法が指定されていないことに注意してください。したがって、あなたの本の言うことは、すべてのプラットフォームではなく、一部のプラットフォームに当てはまるかもしれません。

28
interjay

C言語標準の規定を説明するものとして、あなたのテキストは不当な主張をしています。具体的には、標準notunsigned intがあらゆる種類の構造の基本レイアウト単位であると言うだけでなく、ビットフィールド表現が保存されるストレージユニットのサイズ:

実装では、ビットフィールドを保持するのに十分な大きさのアドレス可能なストレージユニットを割り当てることができます。

C2011、6.7.2.1/11

このテキストは、標準でサポートされていないパディングについても想定しています。 C実装では、structの任意またはすべての要素とビットフィールドストレージユニットの最後に任意の量のパディングを自由に含めることができます。実装では通常、この自由度を使用してデータアライメントの考慮事項に対処しますが、Cはその使用にパディングを制限しません。これは、テキストで「パディング」と呼ばれる名前のないビットフィールドとはまったく別のものです。

しかし、Cは、ビットフィールドの宣言されたデータ型が、その表現が存在するストレージユニットのサイズと関係があるという悲惨な一般的な誤解を避けるために、この本を賞賛すべきだと思います。標準では、このような関連付けは行われません。

私のコンパイラでは、まったく同じ構造がパディングありの16ビット(ビットではなく)とパディングなしの16バイトを測定するのはなぜですか?

テキストをできるだけスラックするために、メンバーが占有するデータのビット数(合計16ビット、名前のないビットフィールドに属する6)とstructのインスタンスの全体サイズを区別します。全体の構造はunsigned intのサイズになると断言しているようです。これは、説明しているシステム上では明らかに32ビットであり、構造体の両方のバージョンで同じです。

原則として、観測されたサイズは、ビットフィールドに128ビットのストレージユニットを使用する実装によって説明できます。実際には、1つ以上のより小さなストレージユニットを使用する可能性が高いため、各構造体の余分なスペースの一部は、上記で触れたような実装提供のパディングに起因します。

C実装では、すべての構造タイプに最小サイズを課すことは非常に一般的であるため、必要に応じて表現をそのサイズにパディングします。多くの場合、このサイズは、システムでサポートされているすべてのデータ型の最も厳しいアライメント要件に一致しますが、これも言語の要件ではなく実装上の考慮事項です。

結論:実装の詳細や拡張機能に依存することによってのみ、ビットフィールドメンバーの有無にかかわらず、structの正確なサイズを予測できます。

8
John Bollinger

驚いたことに、いくつかのGCC 4.9.2オンラインコンパイラには違いがあるようです。まず、これは私のコードです:

#include <stdio.h>
#include <stdbool.h>

struct test {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};

struct test_packed {
       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
} __attribute__((packed));

int main(void)
{
       struct test padding;
       struct test_packed no_padding;

       printf("with padding: %zu bytes = %zu bits\n", sizeof(padding), sizeof(padding) * 8);
       printf("without padding: %zu bytes = %zu bits\n", sizeof(no_padding), sizeof(no_padding) * 8);

       return 0;
}

そして今、異なるコンパイラからの結果。

WandBoxのGCC 4.9.2:

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

http://cpp.sh/ のGCC 4.9.2:

with padding: 4 bytes = 32 bits
without padding: 2 bytes = 16 bits

だが

Onlinecompiler.comのGCC 4.9.2:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits

(適切にコンパイルするには、%zu%uに変更する必要があります)

[〜#〜] edit [〜#〜]

@interjayの answer がこれを説明するかもしれません。 -mms-bitfieldsをWandBoxからGCC 4.9.2に追加すると、次のようになりました:

with padding: 16 bytes = 128 bits
without padding: 16 bytes = 128 bits

C標準では、変数をメモリに配置する方法に関するすべての詳細が説明されているわけではありません。これにより、使用するプラットフォームに依存する最適化の余地が残ります。

物事がメモリ内にどのように配置されているかを自分の考えに与えるには、次のようにします。

#include <stdio.h>
#include <stdbool.h>

struct test{

       bool opaque                 : 1;
       unsigned int fill_color     : 3;
       unsigned int                : 4;
       bool show_border            : 1;
       unsigned int border_color   : 3;
       unsigned int border_style   : 2;
       unsigned int                : 2;
};


int main(void)
{
  struct test Test = {0};
  int i;
  printf("%zu\n", sizeof(Test));

  unsigned char* p;
  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.opaque = true;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  Test.fill_color = 3;

  p = (unsigned char*)&Test;
  for(i=0; i<sizeof(Test); ++i)
  {
    printf("%02x", *p);
    ++p;
  }
  printf("\n");

  return 0;
}

これをideoneで実行する( https://ideone.com/wbR5tI

4
00000000
01000000
07000000

opaquefill_colorは両方とも最初のバイトにあります。 (gccを使用して)Windowsマシン上でまったく同じコードを実行すると、次の結果が得られます。

16
00000000000000000000000000000000
01000000000000000000000000000000
01000000030000000000000000000000

ここで、opaquefill_colorは両方とも最初のバイトにnotです。 opaqueは4バイトを占めるようです。

これは、合計16バイトを取得することを説明します。つまり、boolはそれぞれ4バイトを使用し、その後のフィールドとその後のフィールドでは4バイトを使用します。

6
4386427

あなたは本が言っていることを完全に誤解しています。

宣言された16ビットのビットフィールドがあります。 6ビットは、何にも使用できない名前のないフィールドです-それが言及されているパディングです。 16ビットから6ビットを引いた値は10ビットに相当します。パディングフィールドをカウントしないで、構造体には10ビットの有用なビットがあります。

構造体のバイト数は、コンパイラの品質に依存します。どうやら、boolビットフィールドを構造体にパックしないコンパイラに遭遇したようです。boolには4バイト、ビットフィールドにはいくらかのメモリ、さらに構造体のパディング、合計4バイト、boolにはさらに4バイト、ビットフィールドにはさらにメモリを使用します、さらに構造体のパディング、合計4バイト、合計16バイト。本当に悲しいです。この構造体は、かなり合理的に2バイトです。

2
gnasher729

歴史的に、ビットフィールド要素のタイプを解釈する2つの一般的な方法がありました。

  1. 型が符号付きか符号なしかを調べますが、要素を配置する場所を決定する際に「char」、「short」、「int」などの区別を無視します。

  2. ビットフィールドの前に同じ型の別のビットフィールド、または対応する符号付き/符号なしのタイプがある場合を除き、そのタイプのオブジェクトを割り当て、ビットフィールドをその中に配置します。適合する場合は、そのオブジェクトに同じタイプの次のビットフィールドを配置します。

#2の背後にある動機は、16ビット値をWordに揃える必要があるプラットフォームで、コンパイラが次のようなものを与えたからだと思います。

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 7;
};

両方のフィールドが2番目のバイトに配置された2バイト構造を割り当てることができるかもしれませんが、構造が次の場合:

struct foo {
  char x;  // Not a bitfield
  someType f1 : 1;
  someType f2 : 15;
};

f2のすべてが単一の16ビットWord内に収まる必要があるため、f1の前にパディングバイトが必要になります。共通の初期シーケンスルールのため、f1はこれら2つの構造に同じように配置する必要があります。これは、f1が共通の初期シーケンスルールを満たすことができる場合、最初の構造。

現状では、最初のケースでより密なレイアウトを可能にしたいコードは次のように言えます:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned char f2 : 7;
};

xの直後のバイトに両方のビットフィールドを入れるようにコンパイラーを招待します。型はunsigned charとして指定されているため、コンパイラは15ビットフィールドの可能性について心配する必要はありません。レイアウトが次の場合:

struct foo {
  char x;  // Not a bitfield
  unsigned short f1 : 1;
  unsigned short f2 : 7;
};

意図はf1f2が同じストレージに配置され、コンパイラーはその「bunkmate」f2。コードが次の場合:

struct foo {
  char x;  // Not a bitfield
  unsigned char f1 : 1;
  unsigned short f2 : 15;
};

次に、f1xの後に配置され、f2が単独でWordに配置されます。

C89標準は、ストレージがf1を使用する前にf2が1バイトに配置されないようにするレイアウトを強制する構文を追加したことに注意してください。

struct foo {
  char x;  // Not a bitfield
  unsigned short : 0;  // Forces next bitfield to start at a "short" boundary
  unsigned short f1 : 1;
  unsigned short f2 : 15;
};

C89に:0構文を追加すると、古いコードを処理する場合を除き、コンパイラーが型の変更を強制的にアライメントと見なす必要がほとんどなくなります。

1
supercat