ビットフィールドを梱包するときにVC ++は何をしていますか?
-
29-09-2019 - |
質問
私の質問を明確にするために、例プログラムから始めましょう。
#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バイトに詰め込まれており、バイト全体を無駄にしているようです。コンパイラがこれらのビットをメモリに敷設していることに興味がありますが、私は特定の価値を書いてみました &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つのサイズを手に入れる理由について誰かが私にいくつかのアイデアを与えることができますか?
解決
MSVC ++は、ビットフィールドに使用したタイプに対応するメモリの単位を常に割り当てます。使いました unsigned int
, 、それを意味します unsigned int
最初に割り当てられ、別のものが割り当てられます unsigned int
最初のものが使い果たされると割り当てられます。 MSVC ++に2番目の未使用部分をトリミングするように強制する方法はありません unsigned int
.
基本的に、MSVC ++はあなたを解釈します unsigned int
表現する方法として アライメント要件 構造全体の場合。
ビットフィールドには小さなタイプを使用します(unsigned short
と unsigned char
)そして、彼らが割り当てられたユニットを完全に埋めるようにビットフィールドを再編成します - そうすれば、あなたはできるだけしっかりと物事を詰めることができるはずです。
他のヒント
ビットフィールドは、定義するタイプに保存されます。あなたが使用しているので unsigned int
, 、そしてそれは単一に収まりません unsigned int
その後、コンパイラは2番目の整数を使用し、最後の整数に最後の24ビットを保存する必要があります。
さて、この場合、たまたま32ビットであるunsigned intを使用しています。署名されていないINTの次の境界(ビットフィールドに適合)は、64ビット=> 8バイトです。
PSTは正しいです。 メンバー 1バイトの境界に並べられています(またはビットフィールドであるため、より小さく)。全体の構造はサイズ8で、8バイトの境界に並べられています。これは、標準と両方に準拠しています pack
オプション。ドキュメントは、最後にパディングがないと言うことはありません。
別の興味深いことを示すために、何が起こっているのか、型境界を横切る構造を詰めたい場合を考えてください。例えば
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.a = 1;
(*(struct state_cost *)p_sc).cost = 12345;
sc.b = 1;
誰かがこれを行うためのよりエレガントな方法を知っているなら、私は知りたいです!