8

私の質問を明確にするために、サンプルプログラムから始めましょう:

#include <stdio.h>

#pragma pack(push,1)
struct cc {
    unsigned int a   :  3;  
    unsigned int b   : 16;
    unsigned int c   :  1;
    unsigned int d   :  1;
    unsigned int e   :  1;
    unsigned int f   :  1;
    unsigned int g   :  1;
    unsigned int h   :  1;
    unsigned int i   :  6;  
    unsigned int j   :  6;  
    unsigned int k   :  4;  
    unsigned int l   : 15;
};
#pragma pack(pop)

struct cc c;

int main(int argc, char **argv)

{   printf("%d\n",sizeof(c));
}

出力は「8」です。これは、パックしたい 56 ビット (7 バイト) が 8 バイトにパックされていることを意味し、1 バイトを無駄にしているように見えます。コンパイラがこれらのビットをどのようにメモリに配置していたのか知​​りたくて、特定の値を に書き込んでみました&c

int main(int argc, char **argv)

{
unsigned long long int* pint = &c;
*pint = 0xFFFFFFFF;
printf("c.a = %d", c.a);
...
printf("c.l = %d", c.l);
}

予想どおり、Visual Studio 2010 を使用する x86_64 では、次のことが起こります。

*pint = 0x00000000 000000FF :

c[0].a = 7
c[0].b = 1
c[0].c = 1
c[0].d = 1
c[0].e = 1
c[0].f = 1
c[0].g = 0
c[0].h = 0
c[0].i = 0
c[0].j = 0
c[0].k = 0
c[0].l = 0

*pint = 0x00000000 0000FF00 :

c[0].a = 0
c[0].b = 0
c[0].c = 0
c[0].d = 0
c[0].e = 0
c[0].f = 0
c[0].g = 1
c[0].h = 127
c[0].i = 0
c[0].j = 0
c[0].k = 0
c[0].l = 0


*pint = 0x00000000 00FF0000 :

c[0].a = 0
c[0].b = 0
c[0].c = 0
c[0].d = 0
c[0].e = 0
c[0].f = 0
c[0].g = 0
c[0].h = 32640
c[0].i = 0
c[0].j = 0
c[0].k = 0
c[0].l = 0

移植性についてはしばらく忘れて、1 つの CPU、1 つのコンパイラ、および 1 つのランタイム環境に関心があると仮定してください。VC++ がこの構造を 7 バイトにパックできないのはなぜですか? それは言葉の長さですか?MSDNのドキュメント#pragma packは、「メンバーの配置は、n の倍数 [私の場合は 1] またはメンバーのサイズの倍数のいずれか小さい方の境界上にある」と記載されています。7 ではなく 8 のサイズを取得する理由を誰か教えてもらえますか?

4

5 に答える 5

7

MSVC++ は常に、ビット フィールドに使用した型に対応するメモリ ユニットを少なくとも割り当てます。を使用しました。これは、最初に a が割り当てられ、最初のものが使い果たされたときに別unsigned intの a が割り当てられることを意味します。MSVC++ に秒の未使用部分を強制的に削除させる方法はありません。unsigned intunsigned intunsigned int

基本的に、MSVC++ は構造体全体のアラインメント要件unsigned intを表現する方法としてyour を解釈します。

unsigned shortビットフィールド (および)にはより小さい型を使用unsigned charし、割り当てられたユニットを完全に埋めるようにビットフィールドを再グループ化します。

于 2010-10-12T21:51:14.637 に答える
3

ビットフィールドは、定義した型で格納されます。を使用してunsigned intいて、1 つに収まらないためunsigned int、コンパイラは 2 番目の整数を使用し、最後の 24 ビットをその最後の整数に格納する必要があります。

于 2010-10-12T21:36:46.207 に答える
1

この場合、たまたま 32 ビットの unsigned int を使用しています。unsigned int の次の境界 (ビットフィールドに収まる) は 64 ビット => 8 バイトです。

于 2010-10-12T21:38:23.987 に答える
0

何が起こっているのかを別の興味深い例として説明するために、型の境界を越える構造をパックしたい場合を考えてみましょう。例えば

struct state {
    unsigned int cost     : 24; 
    unsigned int back     : 21; 
    unsigned int a        :  1; 
    unsigned int b        :  1; 
    unsigned int c        :  1;
};

私の知る限り、この構造は MSVC を使用して 6 バイトにパックすることはできません。ただし、最初の 2 つのフィールドを分割することで、目的のパッキング効果を得ることができます。

struct state_packed {
    unsigned short cost_1   : 16; 
    unsigned char  cost_2   :  8;
    unsigned short back_1   : 16; 
    unsigned char  back_2   :  5;
    unsigned char  a        :  1; 
    unsigned char  b        :  1; 
    unsigned char  c        :  1; 
};

これは実際に 6 バイトにパックできます。ただし、元のコスト フィールドへのアクセスは非常に厄介で見苦しいものです。1 つの方法は、state_packed ポインターを特殊なダミー構造体にキャストすることです。

struct state_cost {
    unsigned int cost     : 24;
    unsigned int junk     :  8; 
};

state_packed    sc;
state_packed *p_sc = &sc;

sc.a = 1;
(*(struct state_cost *)p_sc).cost = 12345;
sc.b = 1;

誰かがこれを行うためのよりエレガントな方法を知っているなら、私は知りたいです!

于 2010-10-20T21:02:21.697 に答える
0

pstは正しいです。メンバーは、1 バイト境界 (ビットフィールドであるため、それ以下) に配置されます。構造体全体のサイズは 8 で、8 バイト境界に位置合わせされます。packこれは、標準とオプションの両方に準拠しています。ドキュメントは、最後にパディングがないとは決して言いません。

于 2010-10-12T21:39:54.290 に答える