C++ 什么是VC++;打包位字段时执行什么操作?
为了澄清我的问题,让我们从一个示例程序开始:C++ 什么是VC++;打包位字段时执行什么操作?,c++,visual-c++,bit-fields,bit-packing,C++,Visual C++,Bit Fields,Bit Packing,为了澄清我的问题,让我们从一个示例程序开始: #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;
#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));
}
可以预见,在使用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个字节?它是一个字长的东西吗?on
#pragma pack
上说“成员的对齐将在一个边界上,该边界是n[1]的倍数或成员大小的倍数,以较小者为准。”谁能告诉我为什么我得到的大小是8而不是7?位字段存储在您定义的类型中。由于您使用的是无符号整数
,并且它不适合单个无符号整数
,因此编译器必须使用第二个整数,并将最后24位存储在最后一个整数中。您使用的是无符号整数,在这种情况下正好是32位。unsigned int的下一个边界(适合位字段)是64位=>8字节。pst是正确的。成员在1字节边界上对齐(或更小,因为它是位字段)。整个结构的大小为8,并在8字节边界上对齐。这符合标准和pack
选项。文档从不说末尾没有填充。MSVC++总是至少分配一个与您用于位字段的类型相对应的内存单元。您使用了unsigned int
,这意味着一个unsigned int
最初被分配,而另一个unsigned int
在第一个int耗尽时被分配。无法强制MSVC++修剪第二个无符号int
中未使用的部分
基本上,MSVC++将unsigned int
解释为表示整个结构的对齐要求的一种方式
为您的位字段使用较小的类型(
unsigned short
和unsigned char
),并重新组合位字段,使它们完全填充分配的单元-这样您应该能够尽可能紧密地打包东西。给出另一个有趣的示例,说明发生了什么,考虑要打包跨越类型边界的结构的情况。例如
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个字节。然而,访问原始成本字段是非常尴尬和丑陋的。一种方法是将state_压缩指针强制转换为专用的虚拟结构:
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;
如果有人知道更优雅的方法,我很想知道 文件上说“……将在……的边界上”;但是,我找不到关于大小保证的地方。在这种情况下,如果他需要15位和16位,他不能保存任何内容,他宁愿最后至少有9个字节,因为他已经使用了56位。不。。。有了这个建议,我真的可以把它打包成7个字节(谢谢AndreyT)。我不知道你说的9字节是什么意思。@Rooke:注意,
无符号字符位:10
是有效的,但可能不是你说的意思(最多只能存储8位值,并为填充保留2个额外的位)。也就是说,重新组合位字段以使每个位字段都适合所分配的单元是至关重要的。@David Rodríguez-dribeas:的确如此。解决方案要求对字段进行重新排序。问题不在于结尾处的填充,而在于位字段被压缩在位字段类型的单位内。该结构完全没有填充,只有两个无符号int
成员。@David,该标准说(§6.7.2.1),“一个位字段被解释为一个有符号或无符号整数类型,由指定数量的位组成。[…]一个实现可以分配任何足以容纳一个位字段的可寻址存储单元。”所以我不认为unsigned int
意味着它必须实际使用unsigned int
作为存储单元,只是一些具有足够位的无符号类型。此外,位字段允许跨越存储单元边界。所以我确实认为结尾有填充物。顺便问一下,你看的是什么标准?目前的C++标准和C++ 0x FCD都没有第7.7.2.1节,部分标准至少对我来说是个混淆。我对该论点的理由是§9.6/1包含:常量表达式[位数]可能大于位字段类型的对象表示(3.9)中的位数;在这种情况下,额外位用作填充位,不参与位字段的值表示(3.9)。这意味着无符号字符位:10
将无法存储10位整数(在8bit/char机器中),并且将不同于无符号短整型位:10
,(假设为16位短整型)。根据这一点,我推断——我不能指出标准——如果编译器不能升级到更大的表示类型,那么它可能也不允许降级。此外,它还必须将表示类型从int
中断为short+char
,在这种特定情况下(再次假设为8位char,16位短,32位int),这是错误的。编译器可以用它想要的任何类型来存储位字段。@Abyx这与这里的其他答案和注释相反。如果你有什么东西支持你的主张,我们想看看。
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;