在c语言中提供一个一种叫 “位域” 或者 “位段” 的数据结构。它的存在是为了更加的节省空间。因为在有些实际需求中,并不需要占用一个完整的字节,而只是需要一个或者几个二进制位。比如存在一个开关量时,只有 0 和 1 两种状态,只需要一个二进制位存储即可。
位域的定义如下:
struct 位域结构名{ 位域列表 };
其中位域列表的形式为:类型说明符 位域名:位域长度
例如,
位域的变量的说明 与结构体的方式一样,有三种方式:先定义后说明、同时定义说明、直接说明。
在声明时,位段成员必须是整形或枚举类型(通常是无符号类型)。
结构中也可以包含无名位域,作为相邻成员之间的填充或调整位置。无名位域无法被引用,它们的内容在运行时是不可预测的。
位域的定义有如下限制
1、一个位域必须存储在同一个字节中,不能跨两个字节。
比如当一个字节所剩的空间不够下一个位域存储时,则从下一个存储单元的起始位置开始存放;也可以专门让某个位域从下一个存储单元的起始位置开始。
在这个位域中,a 占第一个字节的前 4 位,a 后面 2 位进行填充,第一个字节剩余的 2 位不够 b 存储,因此 b 从下一个字节开始存储,b 后面的位域填充 0,c 从下一个单元存储,该下一个单元指的是下一个 unsigned int,因为空位域 0 会把 b 后面 unsigned int 所剩的空间全部填充 0, 因此 sizeof(ss) = 8。
2、位域可以是无名域,无名域就是类似于 unsigned int : 0; 或者 unsigned int : 2; 虽然两者只是 0 和非 0 的区别,但是作用却是不同的。若无名域的位数为 0,则下一个位域将会强制从下一个单元开始(这里的一个单元指的不是下一个字节,而是跨过跨过一次数据类型的自然边界);如果无名位域为非 0,则意味着这个无名位域占着空间,不能被使用。
在该例子中 a 占 unsigned int类型所占字节的3位,a 后面的无名域会把 a 后面的所有位填充 0,因此对于 unsigned int 类型所占的 4 字节空间,除 a 的 3 位外,其他位均为0,b 从下一个 unsigned int 类型开始,所以 sizeof(tt) 为 8。
a后面无名域占 2 位,b跟在无名域后面,所以 a 和 b共占用一个字节。因此sizeof(tt) 为 4。
3、如果相邻位域字段的类型相同,且其位宽之和小于类型的 sizeof 大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止。
a 和 b 所占的宽度之和小于 sizeof(unsigned char)大小,因此 a和b共同使用一个字节,所以 sizeof(t) 为 1;
4、如果相邻位域字段的类型相同,但其位宽之和大于类型的 sizeof 大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍。
由于 a 和 b 宽度之和大于 sizeof(unsigned int)大小,因此b从下一个 unsigned int 处开始,所以 sizeof(t) 为 8。
5、整个结构体的总大小为最宽基本类型成员大小的整数倍。
整个结构体的总大小为 unsigned int 类型的整数倍。
6、如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式。(跟编译器有较大的关系,使用时要慎重,尽量避免)
对于该类型,不压缩时,a和b类型相同会占用一个字节,c会占用一个字节,d会单独存放,不会和共占用一个字节,因为整个结构体的总大小为最宽基本类型成员大小的整数倍,因此 sizefo(t)为 8。
若压缩时(gcc下),c 和 d 共占用一个字节,因为整个结构体的总大小为最宽基本类型成员大小的整数倍,因此 sizefo(t)为 4。
7、 如果位域字段之间穿插着非位域字段,则不进行压缩;(不针对所有的编译器,跟编译器有较大的关系,使用时要慎重,尽量避免)
sizeof(t) 大小为 12。
8、当使用有符号类型来定义位域,并且无意中使用到了正负(有意或者无意)特性时,可能出现不是想要的结果。
可见,当为域的最高位是 1 的时候,会进行符号扩展,而且这也取决于编译器的实现,因此,为避免此类问题,最好使用无符号类型定义位域。
9、取地址操作符 & 不能应用在位域字段上,因此不存在位域的指针。
10、位域字段不能是静态成员。
11、不能用来指定位数的类型。
若 struct 成员是指针变量类型不能用来指定所占的位数,在 64 位系统中指针固定占 8 字节,在 32 位系统中指针固定占 4 字节。
若 struct 成员是 double 或 float 类型,不能指定位数,否则编译出错,位域类型无效。
原作者:Linux码农