位域结构体简介
Contents
最近实习接触到一个新的知识点,C/C++ 的位域结构体。
以下开始摘抄自:here
位段 (bit-field) 是以位为单位来定义结构体 (或联合体) 中的成员变量所占的空间。含有位段的结构体 (联合体) 称为位段结构。采用位段结构既能够节省空间,又方便于操作。
位段的定义格式为:
type [var]: digits
其中 type 只能为 int,unsigned int,signed int 三种类型 (int 型能不能表示负数视编译器而定,比如 VC 中 int 就默认是 signed int,能够表示负数)。位段名称 var 是可选参数,即可以省略。digits 表示该位段所占的二进制位数。
举个例子,你可以这样定义一个位域结构体:
|
|
uint32_t
实际上是unsigned int
的别名,并且指定了用 4 个字节存储 int 类型的数据,而 1byte = 8bits, 4 个字节共计 32bits,结构体 A 使用了位域的方式,指定了每个成员所占用的 bit 数。可以看到,A 中三个成员总共占用的比特数为 32,也就是 4 个字节。所以结构体 A 所占用的空间就是 4 字节,而 B 没有使用位域,则 3 个成员各占 4 个字节,共计 12 字节。说白了,位域就是将结构体的成员在比特位上编排的更紧凑,更节省空间。
以下开启一段摘抄:
关于位域结构体有以下几点说明:(以下“位段就是位域”,c.f. ref1)
- 位段的类型只能是 int,unsigned int,signed int 三种类型,不能是 char 型或者浮点型;
- 位段占的二进制位数不能超过该基本类型所能表示的最大位数,比如在 VC 中 int 是占 4 个字节,那么最多只能是 32 位;
- 无名位段不能被访问,但是会占据空间;
- 不能对位段进行取地址操作;
- 若位段占的二进制位数为 0,则这个位段必须是无名位段,下一个位段从下一个位段存储单元 (这里的位段存储单元经测试在 VC 环境下是 4 个字节) 开始存放;
- 若位段出现在表达式中,则会自动进行整型升级,自动转换为 int 型或者 unsigned int。
- 对位段赋值时,最好不要超过位段所能表示的最大范围,否则可能会造成意想不到的结果。
- 位段不能出现数组的形式。
对于位段结构,编译器会自动进行存储空间的优化,主要有这几条原则:
- 如果一个位段存储单元能够存储得下位段结构中的所有成员,那么位段结构中的所有成员只能放在一个位段存储单元中,不能放在两个位段存储单元中;如果一个位段存储单元不能容纳下位段结构中的所有成员,那么从剩余的位段从下一个位段存储单元开始存放。(在 VC 中位段存储单元的大小是 4 字节).
- 如果一个位段结构中只有一个占有 0 位的无名位段,则只占 1 或 0 字节的空间 (C 语言中是占 0 字节,而 C++ 中占 1 字节);否则其他任何情况下,一个位段结构所占的空间至少是一个位段存储单元的大小;
|
|
以下测试用法:
|
|
上述程序的输出为:
length: 65535
flags: 255
num: -1
解释一下为什么 num 成员的值为 -1: 首先 num 设置的比特位为 8,而 num 的类型为 int,是有符号的。对于有符号的整数,计算机内部使用补码表示的。 0xff 换成二进制 1111 1111 这正好是 -1 的补码。
其实,Ycm 给出的提示已经很明确了: Ycm 提示这里发生了隐式截断。255 被截断成了 -1. 如果换成 0x11 也就是二进制的 0001 0001,则不会发生截断,因为 8 比特足够描述 0x11,符号位是 0,表示正数,所以这很符合我们的预期。但是如果再加一个 2 呢? 可以看到 Ycm 提示发生截断, 529 被截断成了 17,即 0010 0001 0001 被截断成了 0001 0001 也就是说,我结构体定义的时候已经确定了 num 只有 8 比特位可以存。高于 8 比特的数据全都截断。如果最高位是 1,则会被转成负数,这可能和你的预期不符。所以一定不要设置超过容量的数据。