驱动开发人员或者经常与协议规范打交道的工程师对位域肯定不陌生。当我们需要用C语言数据类型来表示软硬件平台指定的描述符结构,以及某些网络协议的包格式时;或者描述为了节省内存而自定义的紧凑数据结构时;为了可读性,编码的方便性,我们会使用使用位域(Bit-Field)。本文将探讨位域的基本概念,使用细节和一些注意项。
位域——基本概念
带有预定义宽度的变量被称为位域,形式如下:
struct 位域结构名 {
类型说明符 位域名:位域长度
....
};
类型说明:定义了位域的类型,位域只能是整数类型,在C语言规范中规定的类型包括int,uint32_t和signed int,至于其它整数类型,如(unsigned) char, (unsigned) short, (unsigned) long, (unsigned) long long等,是否可用作位域的存储单元,都取决于编译器的实现。(大多数主流编译器都允许:在目标体系架构上所支持的所有的整数类型,均可用作位域的类型说明。)
位域名:顾名思义是位域的名称
位域长度:代表位域占用的bit位的数量,宽度必须小于或等于指定类型的位宽度。
举个栗子:
struct foo {
uint32_t a:8;
uint32_t b:2;
uint32_t c:6;
};
由位域的定义我们可以知道,位域的分配是基于比特的。由于绝大多数计算机(如果不是全部的话)都以字节为单位进行编址,这就意味着,你不能对位域进行以下操作:
a. 对一个位域元素进行取地址操作
b. 对一个位域元素进行指针操作
c. 定义位域元素数组
d. 进行sizeof运算
位域——排布规则
C99规定:“在一个存储单元内,如果之前的位域分配之后,还剩下足够的比特位以分配紧随其后定义的下一个位域,则应该在这个存储单元内,从第一个空闲比特位开始为这个位域进行分配”,除此之外,C标准没有再定义任何与位域分配相关的标准,这就给了编译器相当大的自由度。我们分析以下三种场景
a. 场景一:如果相邻位域元素的类型相同,且其位宽之和小于类型的sizeof大小,则后面的元素将紧邻前一个元素存储,直到不能容纳为止
这一场景是C标准规定的内容,很好理解,所以在上面的栗子中,位域元素a,b,c会在一个uint32_t存储单元内分配。这是由于a,b,c类型相同,且位宽之和(8+6+2)小于32。
b. 场景二:如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,这种跨越边界的场景,C规范并没有定义,编译器自行实现分配方式。例如:
struct foo {
uint32_t a:8;
uint32_t b:28;
uint32_t c:28;
};
在自然对齐的情况下,GCC会将b,c分配在新的存储单元,不会让b,c横跨uint32_t的存储单元边界。整个数据结构占用12个字节。
但如果使用#pragma pack(n)来指定成员对齐,无论n为何值,b,c都将横跨存储单元的边界,如下
#pragma pack(n)