问题描述
这个结构体的大小是如何计算的:
struct B {
unsigned char c1 : 1;
unsigned char : 2;
unsigned char c2 : 2;
unsigned char : 0;
unsigned char c3 : 4;
unsigned char c4 : 1;
};
这个 0
在这里代表什么?,这是否意味着它占用了 0 位(即没有内存)
有人可以向我解释一下它的大小是如何计算的吗?
解决方法
大小为 0 的位域用于指定任何后续位域都将放置在单独的字节/单元中。所以结构的布局可能如下所示:
| c1| | c2 | | c3 | c4| |
-----------------------------------------------------------------
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15|
-----------------------------------------------------------------
如果没有大小为 0 的字段,它可能看起来像这样:
| c1| | c2 | c3 | c4| |
-----------------------------------------------------------------
| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15|
-----------------------------------------------------------------
但是请注意,结构体中位域的顺序是由实现定义的,因此它可能看起来不完全像这样。
,0
的位域长度意味着开始一个新的存储单元。
问题中结构的一种可能布局是:
- 成员
c1
、长度为 2 的未命名成员和成员c2
的初始存储单元, - 成员
c3
和c4
的单独存储单元。
这两个存储单元都可以小到一个字节,因为每个都只包含 5 位信息,使 struct B
成为一个 2 字节的对象,即:sizeof(struct B) == 2
。但是其他大小也是可能的,因为存储单元的选择是实现定义的,并且成员之间和结构末尾的额外填充也是可能的。任何大于 2
的大小都是可能的。有争议的是,即使在一个字节可以存储至少 10 位的体系结构上,1
的大小也是不可能的,因为 unsigned char : 0;
成员规范需要 2 个存储单元。
以下是 C 标准的定义:
6.7.2.1 结构和联合说明符
限制条件
[...]
4 指定位域宽度的表达式应为整数常量表达式,其非负值不超过指定类型的对象的宽度,即冒号和表达式省略。如果值为零,则声明应没有声明符。
5 位域的类型应为 _Bool
、signed int
、unsigned int
或其他一些实现的合格或不合格版本-定义的类型。是否允许原子类型由实现定义。
语义
[...]
9 结构或联合的成员可以具有除可变修改类型之外的任何完整对象类型。此外,可以声明成员由指定数量的位(包括符号位,如果有)组成。这样的成员称为位域;它的宽度前面有一个冒号。
10 位域被解释为具有由指定位数组成的有符号或无符号整数类型。如果值 0 或 1 存储到类型为 _Bool
的非零宽度位域中,则位域的值应与存储的值相等; _Bool
位域具有 _Bool
的语义。
11 一个实现可以分配任何足够大的可寻址存储单元来容纳一个位域。如果剩余足够的空间,紧跟在结构中另一个位域之后的位域应被打包到同一单元的相邻位中。如果剩余空间不足,则不适合的位字段是否放入下一个单元或与相邻单元重叠是实现定义的。单元内位域的分配顺序(高阶到低阶或低阶到高阶)是实现定义的。可寻址存储单元的对齐方式未指定。
12 一个没有声明符,只有一个冒号和一个宽度的位域声明,表示一个未命名的位域。作为一种特殊情况,宽度为 0
的位域结构成员表示没有进一步的位域被打包到前一个位域(如果有的话)所在的单元中。>
因此,实现定义了 c1
和 c2
如何放置在它们的公共存储单元中,c3
和 c4
也是如此。 10意味着分配单元的选择是实现定义的,因此struct B
的大小也是实现定义的:它可以是2
、4
、{{ 1}} 等。事实上,存储单元不需要具有相同的大小,并且允许填充,因此理论上任何大于 8
的大小都是可能的。
位域缺乏更精确和可移植的规范会适得其反,因为它们可以很好地定制以映射物理硬件设备。早期 C 编译器中实现的差异以及与字节序约束相关的实际约束促使规范和实现变化中的这种灵活性,尤其是在有符号位域方面,这让程序员不愿使用此功能。