5.40 パックされていない struct、__packed struct、個々の __packed フィールドを使用した struct の比較と、__packed struct と #pragma でパックされた struct の比較

これらの比較で、構造体をパックするそれぞれの方法の違いを示します。

パックされていない struct 、 __packed struct 、および個々の __packed フィールドを使用した struct の比較

以下の表は、 struct をパックしなかった場合、 struct 全体をパックした場合、 および struct の個々のフィールドをパックした場合のそれぞれの違いを、 struct の 3 種類の実装で示したものです。

表 5-10 パックされていない struct、パックされた struct、および個々にパックされたフィールドを使用した struct を表す C コード

パックされていない struct パックされた struct パックされたフィールド
struct foo
{
    char one;
    short two;
    char three;
    int four;
} c;
__packed struct foo
{
    char one;
    short two;
    char three;
    int four;
} c;
struct foo
{
    char one;
    __packed short two;
    char three;
    int four;
} c;
最初の実装では、 struct がパックされていません。2 番目の実装では、構造体全体が __packed として修飾されています。3 番目の実装では、 __packed 属性が構造体から削除され、個々の非自然境界整列フィールドが __packed として宣言されています。
以下の表は、コンパイラによって生成されたマシンコードが前の表の各実装サンプルでどのように逆アセンブルされるかを示したものです。 いずれの実装の C コードも、オプション -O2 を使用してコンパイルされています。

表 5-11 パックされていない struct、パックされた struct、および個々にパックされたフィールドを使用した struct を表す逆アセンブリコード

パックされていない struct パックされた struct パックされたフィールド
; r0 contains address of c
; char one
LDRB    r1, [r0, #0]
; short two
LDRSH   r2, [r0, #2]
; char three
LDRB    r3, [r0, #4]
; int four
LDR     r12, [r0, #8]
; r0 contains address of c
; char one
LDRB  r1, [r0, #0]
; short two
LDRB  r2, [r0, #1]
LDRSB r12, [r0, #2]
ORR   r2, r12, r2, LSL #8
; char three
LDRB  r3, [r0, #3]
; int four
ADD   r0, r0, #4
BL    __aeabi_uread4
; r0 contains address of c
; char one
LDRB  r1, [r0, #0]
; short two
LDRB  r2, [r0, #1]
LDRSB r12, [r0, #2]
ORR   r2, r12, r2, LSL #8
; char three
LDRB  r3, [r0, #3]
; int four
LDR   r12, [r0, #4]

非境界整列要素へのアクセスをインラインと関数呼び出しのどちらで行うかは、-Ospace および -Otime の各コンパイラオプションによって制御されます。-Otime を使用すると、インラインの非境界整列アクセスが行われます。-Ospace を使用すると、 関数呼び出しによる非境界整列アクセスが行われます。
前述のサンプルのパックされていない struct の逆アセンブリの場合、コンパイラは、境界整列型ワードまたはハーフワードのアドレス上にあるデータに必ずアクセスします。コンパイラがこれを行うことができるのは、 struct の全メンバーがデータ自身と同じサイズの境界上に配置されるように、 struct にパッドが挿入されているためです。
前述のサンプルの __packed struct の逆アセンブリでは、フィールド onethree は、データ自身と同じサイズの境界にデフォルトで境界整列されているので、コンパイラは境界整列型アクセスを行います。コンパイラは、境界整列されていることが識別できるフィールドに対して、境界整列型のワードアクセスまたはハーフワードアクセスを必ず実行します。非境界整列型フィールド two に対しては、複数の境界整列メモリアクセス(LDR/STR/LDM/STM)を特定のシフト命令およびマスク命令と組み合わせて使用し、メモリ内の正しいバイトにアクセスします。コンパイラは、フィールドがデータ自身と同じサイズの境界上にあるかどうかを判定できないので、不明な境界整列の符号なしワードを読み出すための ARM 組み込みアプリケーションバイナリインタフェース(AEABI)ランタイムルーチン __aeabi_uread4 を呼び出して、 フィールド four にアクセスします。
前述のサンプルの個々にパックされたフィールドを使用した struct の逆アセンブリでは、 struct 全体が __packed として修飾されている場合と同様に、フィールド onetwo、および three がアクセスされます。しかし、 struct 全体がパックされる場合と異なり、コンパイラはフィールド four に対してワード境界整列型のアクセスを行います。これは、構造体内の __packed short の存在に基づいてフィールド four がデータ自身と同じサイズの境界上にあることをコンパイラが判定できるからです。

__packed struct と #pragma pack を用いた struct の比較

以下の表は、__packed struct #pragma pack を用いた struct の違いを、 struct の 2 種類の実装で示したものです。

表 5-12 パックされた struct とプラグマパックされた struct の C コード

パックされた struct #pragma pack を用いてパックされた struct
__packed struct foobar
{
    char x;
    short y[10];
};
short get_y0(struct foobar *s)
{
    // Unaligned-capable load
    return *s->y;
}
short *get_y(struct foobar *s)
{
    return s->y;    // Compile error
}
#pragma push
#pragma pack(1)
struct foobar
{
    char x;
    short y[10];
};
#pragma pop
short get_y0(struct foobar *s)
{
    // Unaligned-capable load
    return *s->y;
}
short *get_y(struct foobar *s)
{
    return s->y;    // No error
    // Potentially illegal unaligned load,
    // depending on use of result
}
最初の実装では、 __packed struct のフィールドまたは struct __packed フィールドのアドレスを取得すると、__packed ポインタが生成され、これを__packed 以外のポインタに暗黙的にキャストしようとすると、コンパイラによってタイプエラーが生成されます。その一方 2 番目の実装では、#pragma pack を用いた struct のフィールドのアドレスを取得しても、 __packed で修飾されたポインタは生成されません。ただし、フィールドはその型に対して適切に整列されず、動作が定義されない非境界整列のポインタを逆参照する可能性があります。
関連する概念
5.36 構造体に含まれる非境界整列型フィールド
5.37 構造体全体を packed として宣言する場合のパフォーマンスの低下
関連する参考文書
8.143 -Ospace
8.144 -Otime
10.12 __packed
10.60 __attribute__((packed)) 型属性
10.68 __attribute__((packed)) 変数属性
10.97 #pragma pack(n)
関連情報
Application Binary Interface (ABI) for the ARM Architecture
非機密扱いPDF file icon PDF 版ARM DUI0472LJ
Copyright © 2010-2015 ARM.All rights reserved.