11.4 ARM C および C++ での構造体、共用体、列挙型、ビットフィールド

構造化されたデータ unionenum、および struct の実装について説明します。また、構造体のパディングとビットフィールドの実装についても説明します。

共用体

異なる型のメンバを使用して union のメンバにアクセスするときは、元の型の表現から結果の値を予測できます。エラーは発生しません。

Enumerations

enum 型のオブジェクトは、その enum の範囲を含む最小の整数型で実装されます。
C モード、および --enum_is_int を使用していない C++ モードでは、 enum に正の列挙値のみが含まれている場合、 enum のストレージ型は、以下に示した型のうち、 enum 内の列挙子の範囲に基づく最初の unsigned 型になります。その他のモードでは、 enum に負の列挙値が 1 つでも含まれている場合、 enum のストレージ型は、以下に示した型のうち、 enum 内の列挙子の範囲に基づく最初の型になります。
  • --enum_is_int を使用していない場合は unsigned char
  • --enum_is_int を使用していない場合は signed char
  • --enum_is_int を使用していない場合は unsigned short
  • --enum_is_int を使用していない場合は signed short
  • signed int
  • C で --strict を指定している場合を除き、 unsigned int
  • C で --strict を指定している場合を除き、 signed long long
  • C で --strict を指定している場合を除き、 unsigned long long

  • RVCT 4.0 では、 enum のストレージ型が、リストの最初の unsigned 型になるという説明は、GNU(--gnu)モードにのみ当てはまります。
  • ARM® コンパイラ 4.1 以降では、 enum のストレージ型が、リストの最初の unsigned 型になるという説明は、モードに関係なく当てはまります。
このように enum を実装することで、データサイズを小さくできます。コマンドラインオプション --enum_is_int を使用して、 enum の基底型を最低でも int の幅に設定できます。
詳細については、『Procedure Call Standard for the ARM® Architecture』仕様の C 言語マッピングに関する説明を参照して下さい。

--enum_is_int オプションを使用または使用せずにコンパイルされており、インタフェースまたはデータ構造を共有している変換単位を混合するときは、注意が必要です。
厳密な C では、列挙値は -2147483648 ~ +2147483647(境界値を含む)の範囲の int で表現できる必要があります。列挙値が範囲外の場合、以下の警告が表示されます。
#66:列挙値が "int" の範囲外です
このような値は C++ でも同様に、 unsigned int long long 、または unsigned long long として扱われます。
列挙値が範囲外であることを示す警告が表示されるようにするには、以下のコマンドを使用して警告をエラーに変更します。
armcc --diag_error=66 ...

構造体

ここで説明する内容は以下に適用されます。
  • すべての C 構造体
  • 仮想関数またはベースクラスを使用しないすべての C++ 構造体とクラス
フィールドが正しく整列され、かつ構造自体が正しく整列するように、構造体にはパディングを挿入できます。以下の図は、従来のパックされていない構造体の例を示します。フィールドを正しく整列するために、バイト 1、2、3 にパディングを挿入しています。また、構造体自体を正しく整列するために、バイト 11、12 にパディングを挿入しています。sizeof() 関数では、このパディングを含めた構造体のサイズが返されます。
図 11-1 従来のパックされていない構造体の例
この図を表示するには、ご使用のブラウザが SVG 形式をサポートしている必要があります。ネイティブでサポートしているブラウザをインストールするか、次のような適切なプラグインをインストールします。Adobe SVG Viewer。.

コンパイラでは、構造体の定義方法に応じて、以下のいずれかの方法で構造体にパディングを挿入します。
  • static または extern として定義されている構造は、ゼロでパディングされます。
  • malloc() または auto でスタックまたはヒープ上に定義される構造体には、そのメモリ位置に以前に格納されていた値をそのまま埋め込みます。memcmp() を使用して、このように定義されたパディング済みの構造体を比較することはできません。
--remarks オプションを使用すると、コンパイラが struct にパディングを挿入したときに生成されるメッセージを表示できます。
C++ では、以下のように空のイニシャライザを備えた構造体が許可されます。
struct
{
    int x;
} X = { };
ただし、C をコンパイルしているとき、または --cpp および --c90 オプションを指定して C++ をコンパイルしているときは、エラーが生成されます。

構造体とそのメンバのパックと境界整列

境界整列構造体は、その境界整列の倍数でパッドされます。アライメント値には最大メンバ境界整列または aligned 属性が使用されます。
__attribute__((aligned(n))) は、メンバと構造体の境界整列を許可します。n が未定義の場合は、デフォルトで 8 バイトが使用されます。これは構造体のメンバのレイアウトには影響せず、__packed 構造体では無視されます。
パック構造体とは、構造体自体の境界整列と、構造体内のメンバの境界整列が常に 1 の構造体です。つまり境界整列のない構造体です。
__attribute__((packed))、または非 GNU モードの __packed では、構造体をパックすることができます。構造体に対して使用すると、メンバの境界整列の値が 1 に下がります。メンバに対して使用すると、そのメンバの境界整列の値が 1 に下がります。
#pragma pack(n) は、__attribute__((packed)) の代わりに使用できます。ただし、構造体で使用した場合、その構造体のメンバの境界整列が n と、メンバの型の自然配列の小さい方の値に下がります。メンバに __attribute__((aligned(m))) がある場合、そのメンバは mn の小さい方の値で整列されます。

ビットフィールド

パックされていない構造体では、 ARM コンパイラによってコンテナ内にビットフィールドが割り当てられます。コンテナは、型が宣言され、正しく整列されたオブジェクトです。
ビットフィールドは、コンフィギュレーションに基づいて、指定された最初のフィールドがワード内の最下位アドレスのビットを占有するように割り当てられます。
リトルエンディアン
最下位アドレスが最下位の内容になります。
ビッグエンディアン
最下位アドレスが最上位の内容になります。
ビットフィールドコンテナには、任意の整数型を指定できます。

厳密な 1990 ISO 標準 C では、ビットフィールドに指定できる型は int signed int 、および unsigned int のみです。 int 型以外をビットフィールドに指定すると、コンパイラからエラーが表示されます。
signed 修飾子も unsigned 修飾子も付けずに宣言されたプレーンなビットフィールドは、 unsigned として扱われます。例えば、int x:10 では、10 ビットの符号なし整数が割り当てられます。
ビットフィールドは、以下のように未割り当てのビットが十分にある、適切な型の最初のコンテナに割り当てられます。
struct X
{
    int x:10;
    int y:20;
};
最初の宣言では、整数コンテナを作成し、x に 10 ビットを割り当てます。2 番目の宣言では、コンパイラによって未割り当てビットが十分にある既存の整数コンテナが検索され、x と同じコンテナ内に y を割り当てます。
1 つのビットフィールドは、コンテナ内に完全に含まれます。コンテナ内に収まらないビットフィールドは、次の同じ型のコンテナ内に配置されます。例えば、以下のように上記の構造体にビットフィールド z を追加で宣言すると、コンテナがオーバーフローします。
struct X
{
    int x:10;
    int y:20;
    int z:5;
};
コンパイラでは、最初のコンテナの残り 2 ビットにパディングを挿入し、新しい整数コンテナを z に割り当てます。
ビットフィールドコンテナは、以下のように互いに重複させることができます。
struct X
{
    int x:10;
    char y:2;
};
最初の宣言では、整数コンテナを作成し、x に 10 ビットを割り当てます。これらの 10 ビットは、整数コンテナの先頭バイトと 2 バイト目の 2 ビットを占有します。2 番目の宣言では、コンパイラによって char 型のコンテナがチェックされます。適切なコンテナがないため、コンパイラは正しく整列された char コンテナを新たに割り当てます。
char の自然配列は 1 であるため、コンパイラはそのビットフィールドを完全に収容することができる未割り当てビットを含む最初のバイトを検索します。上記の例では、 int コンテナの 2 番目のバイトのうち、2 ビットが x に割り当てられ、6 ビットが未割り当てです。コンパイラは、 char コンテナの割り当てを前の int コンテナの 2 バイト目から始め、x に割り当てられている最初の 2 ビットをスキップして、2 ビットを y に割り当てます。
y が宣言された char y:8 である場合、コンパイラは 2 バイト目をパッドし、新しい char コンテナを 3 バイト目に割り当てます。これは、ビットフィールドがそのコンテナをオーバーフローすることはできないためです。以下の図は、以下のサンプル構造体のビットフィールドの割り当てを示しています。
struct X
{
    int x:10;
    char y:8;
};
図 11-2 ビットフィールドの割り当て 1
この図を表示するには、ご使用のブラウザが SVG 形式をサポートしている必要があります。ネイティブでサポートしているブラウザをインストールするか、次のような適切なプラグインをインストールします。Adobe SVG Viewer。.

異なる型のコンテナのビットフィールド宣言にも同じ基本規則が適用されます。例えば、上記の例に int ビットフィールドを追加すると以下のようになります。
struct X
{
    int x:10;
    char y:8;
    int z:5;
}
コンパイラは、 int コンテナの割り当てを int x:10 コンテナと同じ位置から始め、バイト境界で整列された char と 5 ビットのビットフィールドを以下のように割り当てます。
図 11-3 ビットフィールドの割り当て 2
この図を表示するには、ご使用のブラウザが SVG 形式をサポートしている必要があります。ネイティブでサポートしているブラウザをインストールするか、次のような適切なプラグインをインストールします。Adobe SVG Viewer。.

名前を付けずにサイズが 0 のビットフィールドを宣言することで、ビットフィールドコンテナに明示的にパディングを挿入できます。コンテナが空でなければ、サイズが 0 のビットフィールドでコンテナが完全に埋められます。その後のビットフィールド宣言は、新しい空のコンテナから始まります。

最適化として、ビットフィールドが書き込まれるときに、コンパイラがコンテナ内のパディングビットを指定されていない値で上書きする場合もあります。この動作は、ビットフィールドの通常の使用には影響を及ぼしません。

ビットフィールドのパックと境界整列

__attribute__((aligned(n))) を使用すると、コンテナだけではなく、ビットフィールドメンバが n バイトで整列されますが、ビットフィールドは最大でもパックされた境界整列に整列されます。__packed 構造体と __attribute__((packed)) 構造体内のビットフィールドでは無視されます。
ビットフィールドメンバのコンテナの境界整列は、そのビットフィールドメンバの境界整列と同じです。ビットフィールドコンテナのサイズは、ビットフィールドに完全に対応する境界整列の最小倍数で、コンテナ型のサイズを超えません。以下のコードサンプルでこのことを示します。
#pragma pack(2)
/* b のコンテナは 2 バイトアラインメント境界で始まり、
* コンテナ型より小さい必要があります(ここでは
* short のサイズ)。b のコンテナはオフセット 0 で(a と重複して)始まることはできません。
* ビットフィールドがオフセット 2 で開始して、コンテナ内に
* 完全に収まらないためです。したがって、b のコンテナはオフセット
* 2 で開始する必要があります。
*
* データレイアウト:      0x11 0x00 0x22 0x22
* コンテナレイアウト:| a  |    |    b    |
*/
struct {
  char a;
  short b : 16;
} var1 = { 0x11, 0x2222 };
  
      
/* b のコンテナは最大 4 バイトです。サイズは、2 バイト、
* または 4 バイトです。これらは
* コンテナ型よりも小さい境界整列の倍数です。4 バイトのコンテナが
* 0 で始まる場合、ビットフィールド b はオフセット 1 で開始して、
* 完全に収まらないためです。
*
* データレイアウト:      0x11 0x22 0x22 0x00
* コンテナレイアウト:| a  |
*                  |         b         |
*/
struct {
  char a;
  int b : 16;
} var2 = { 0x11, 0x2222 };
パック構造体内のすべてのビットフィールドコンテナを含め、パックされたビットフィールドコンテナの境界整列は 1 です。したがって、パックされたビットフィールドコンテナを境界整列させるために挿入されるビットパディングは最大 7 ビットです。
パックされないビットフィールドコンテナの場合、挿入されるビットパディングは最大 8*sizeof(container-type)–1 です。
構造の配列で要素が正しく整列するように、末端パディングが構造に常に挿入されます。
パックされたビットフィールドコンテナは、宣言したビットフィールドを保持するだけの大きさ(バイト数単位)しかありません。パックされてないビットフィールドコンテナは、それぞれの型のサイズが指定されています。
以下に、これらのインタラクションの例を示します。
struct A {          int z:17; }; // sizeof(A) = 4, alignment = 4
struct A { __packed int z:17; }; // sizeof(A) = 3, alignment = 1
__packed struct A { int z:17; }; // sizeof(A) = 3, alignment = 1
struct A { char y:1;          int z:31; }; // sizeof(A) = 4, alignment = 4
struct A { char y:1; __packed int z:31; }; // sizeof(A) = 4, alignment = 1
__packed struct A { char y:1; int z:31; }; // sizeof(A) = 4, alignment = 1
struct A { char y:1;          int z:32; }; // sizeof(A) = 8, alignment = 4
struct A { char y:1; __packed int z:32; }; // sizeof(A) = 5, alignment = 1
__packed struct A { char y:1; int z:32; }; // sizeof(A) = 5, alignment = 1
struct A { int x; char y:1;          int z:31; };  // sizeof(A) = 8, alignment = 4
struct A { int x; char y:1; __packed int z:31; };  // sizeof(A) = 8, alignment = 4
__packed struct A { int x; char y:1; int z:31; };  // sizeof(A) = 8, alignment = 1
struct A { int x; char y:1;          int z:32; };  // sizeof(A) = 12, alignment = 4 [1]
struct A { int x; char y:1; __packed int z:32; };  // sizeof(A) = 12, alignment = 4 [2]
__packed struct A { int x; char y:1; int z:32; };  // sizeof(A) = 9, alignment = 1
[1] と [2] が同じでない点に注意して下さい。構造体内と端末パディング内の z の位置は異なります。
構造体の例 1
{
int a : 8;  /* オフセット 0 での 4 バイトコンテナ */
__packed int b : 8;  /* オフセット 1 での 1 バイトコンテナ */
__packed int c : 24; /* オフセット 2 での 3 バイトコンテナ */
}; /* 合計サイズ 8(3 バイトの末端パディング)*/;
構造体の例 2
{
__packed int a : 8; /* オフセット 0 での 1 バイトコンテナ */
__packed int b : 8; /* オフセット 1 での 1 バイトコンテナ */
int c : 8; /* オフセット 0 での 4 バイトコンテナ */
}; /* 合計サイズ 4(末端パディングなし)*/
構造体の例 3
{
int a : 8;  /* オフセット 0 での 4 バイトコンテナ */
__packed int b : 32; /* オフセット 1 での 4 バイトコンテナ */
__packed int c : 32; /* オフセット 5 での 4 バイトコンテナ */
int d : 16; /* オフセット 8 での 4 バイトコンテナ */
int e : 16; /* オフセット 12 での 4 バイトコンテナ */
int f : 16; /* 前のコンテナ内 */
}; /* 合計サイズ 16(末端パディングなし)*/
関連する参考文書
10.12 __packed
10.60 __attribute__((packed)) 型属性
10.68 __attribute__((packed)) 変数属性
10.97 #pragma pack(n)
非機密扱いPDF file icon PDF 版ARM DUI0472LJ
Copyright © 2010-2015 ARM.All rights reserved.