void main()
{
struct bitfield
{
signed int a :3;
unsigned int b :13;
unsigned int c :1;
};
struct bitfield bit1 = { 2, 14, 1 };
clrscr();
printf("%d", sizeof(bit1));
getch();
}
为什么这里的大小为 4 个字节?这些元素究竟是如何存储在内存中的?
几乎位字段的每个方面都是实现定义的。甚至“普通int
”位字段的符号也是由实现定义的;它可能已签名或未签名。字段的布局——无论它们是从包含“单元”(标准中使用的术语)中的最高有效位到最低有效位还是从最低到最高有效位都是由实现定义的。最大允许位域的大小;当一个位域被存储在一个新的单元中时;所有这些都是实现定义的。
例如,在使用 GCC 4.8.1 的 Mac OS X 10.8.4 上,可以证明struct bitfield
问题中a
的 3 位最低有效位(0-2 位)b
占用接下来的 13 位(3 -15),并c
占用下一个 1 位 (16):
#include <stdio.h>
static void print_info(int v);
int main(void)
{
int values[] =
{
0x55555555, 0xAAAAAAAA, 0x87654321, 0xFEDCBA98,
0xFEDCBA90, 0xFEDCBA91, 0xFEDCBA92, 0xFEDCBA93,
0xFEDCBA94, 0xFEDCBA95, 0xFEDCBA96, 0xFEDCBA97,
0xFEDCBA98, 0xFEDCBAA0, 0xFEDCBAA8, 0x0000BAA0,
0x0001BAA0, 0x00000008, 0x00000010, 0x00000018,
0x0000FFF0, 0x0000FFF8,
};
for (size_t i = 0; i < sizeof(values)/sizeof(values[0]); i++)
print_info(values[i]);
return 0;
}
static void print_info(int v)
{
union
{
unsigned int x;
struct bitfield
{
signed int a:3;
unsigned int b:13;
unsigned int c:1;
} y;
} u;
u.x = v;
printf("0x%.8X => %2d 0x%.4X %1X\n", u.x, u.y.a, u.y.b, u.y.c);
}
样本输出:
0x55555555 => -3 0x0AAA 1
0xAAAAAAAA => 2 0x1555 0
0x87654321 => 1 0x0864 1
0xFEDCBA98 => 0 0x1753 0
0xFEDCBA90 => 0 0x1752 0
0xFEDCBA91 => 1 0x1752 0
0xFEDCBA92 => 2 0x1752 0
0xFEDCBA93 => 3 0x1752 0
0xFEDCBA94 => -4 0x1752 0
0xFEDCBA95 => -3 0x1752 0
0xFEDCBA96 => -2 0x1752 0
0xFEDCBA97 => -1 0x1752 0
0xFEDCBA98 => 0 0x1753 0
0xFEDCBAA0 => 0 0x1754 0
0xFEDCBAA8 => 0 0x1755 0
0x0000BAA0 => 0 0x1754 0
0x0001BAA0 => 0 0x1754 1
0x00000008 => 0 0x0001 0
0x00000010 => 0 0x0002 0
0x00000018 => 0 0x0003 0
0x0000FFF0 => 0 0x1FFE 0
0x0000FFF8 => 0 0x1FFF 0
测试值不是完全随机选择的。从测试值 0xFEDCBA90 到 0xFECBA97,我们可以看到最低有效 3 位包含a
. 从测试值 0x0000BAA0 和 0x0001BAA0 可以看出,第 17 位(或第 16 位)包含c
. 从测试值 0x00000008 到 0x0000FFF8,我们可以看到位 3-15 包含b
.
然而,必须指出的是,该代码在理论上是可移植的。由于代码写入u.x
然后读取u.x
and u.y.a
,u.y.b
and u.y.c
,它没有访问最后写入的联合成员,这是严格未定义的行为。在实践中,它“总是”有效(我没有听说过它不工作的系统 - 不太可能但技术上不可能有一个系统不工作)。
无论如何,这种布局并不是唯一可能的布局。但是,我无法访问演示替代布局的编译器或系统。
在 ISO/IEC 9899:2011 中,第6.7.2.1 节结构和联合说明符说:
¶11 实现可以分配任何大到足以容纳位域的可寻址存储单元。如果有足够的空间剩余,紧跟在结构中另一个位域之后的位域将被打包到同一单元的相邻位中。如果剩余空间不足,则将不适合的位域放入下一个单元还是与相邻单元重叠是实现定义的。单元内位域的分配顺序(高位到低位或低位到高位)是实现定义的。未指定可寻址存储单元的对齐方式。
¶12 没有声明符但只有冒号和宽度的位域声明表示未命名的位域。126)作为一种特殊情况,宽度为 0 的位域结构成员表示不会将进一步的位域打包到放置前一个位域(如果有的话)的单元中。
126)未命名的位域结构成员可用于填充以符合外部强加的布局。
问题中结构的一个轻微变体是:
struct exegesis
{
signed int a:3;
unsigned int :0;
unsigned int b:13;
unsigned int :0;
unsigned int c:1;
};
此结构的大小为 12(在与以前相同的编译器/平台上)。在这个平台上位域的存储单元是 4 个字节,所以匿名零宽度域开始一个新的存储单元。 a
存储在第一个 4 字节单元的最低有效 3 位中;b
在第二个 4 字节单元的最低有效 13 位中;并且c
在第三个 4 字节单元的最低有效位中。正如标准引用中所述,您也可以拥有大于 0 的匿名位字段。
位域如何存储在内存中取决于实现。
一个可能的原因是,具有 17 位的位域应至少包含 3 个字节,但编译器选择将其填充为 4 个字节。
同样,几乎所有关于位域的内容都取决于实现,包括它们的大小和内存布局。
最后,不要使用void main
,永远留在int main
位字段的存储方式取决于从左到右的小字节序和从右到左的大字节序的架构。内存中的存储发生在字大小。1 个字节是 1 个字。所以在我们的示例中,它的 17 位作为前两个 8 位将存储在前两个字中。下一位将存储在下一个字中。大小应该是 3 字节,但编译器做了一些填充,所以最后大小变成了 4 字节。
对于内存中的存储,您可以参考此链接 http://en.wikipedia.org/wiki/Endianness