打包比特菲尔德时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
等等
暂时忘记便携性,假设您关心一个CPU,一个编译器和一个运行时环境。 VC ++为什么不能将此结构包装到7个字节中?这是一个长篇小说吗?这 MSDN文档 在 #pragma pack
说:“成员的比对将处于n [1]的倍数或成员大小的倍数,以较小者为准。”谁能给我一些想法,为什么我要获得8个尺寸,而不是7个?
解决方案
MSVC ++总是至少分配一个与您用于位场的类型相对应的内存单元。你用了 unsigned int
, ,这意味着一个 unsigned int
最初是分配的,另一个是 unsigned int
当第一个精疲力尽时分配。无法强制MSVC ++修剪第二个未使用的部分 unsigned int
.
基本上,MSVC ++解释您的 unsigned int
作为表达的一种方式 对齐要求 用于整个结构。
使用较小的类型在您的位场(unsigned short
和 unsigned char
)并重新组合位场,以便它们完全填充分配的单元 - 这样您就应该能够尽可能紧紧打包东西。
其他提示
Bitfields存储在您定义的类型中。因为您正在使用 unsigned int
, ,并且它不适合一个 unsigned int
然后,编译器必须使用第二个整数并将最后24位存储在最后一个整数中。
好吧,您正在使用未签名的int,在这种情况下恰好是32位。未签名的INT的下一个边界(适合Bitfield)为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个字节中。但是,我们可以通过分解前两个字段来获得所需的包装效果:
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个字节。但是,访问原始成本字段非常尴尬和丑陋。一种方法是将态度的指针施放为专业的虚拟结构:
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;
如果有人知道这样做的更优雅的方式,我很想知道!