C语言教程-14_3-使用位域进行位操作
引入问题
假设我们需要管理一些文件,其中除了文件的其他信息,我们还需要管理文件的权限,共有以下3种权限:
- 是否可读—r
- 是否可写—w
- 是否可执行—x
每个文件的以上3种权限都不同,现在我们需要设置一个结构体来管理文件.
一种方法是使用3个unsigned int
类型的变量作为标志来存储这些权限信息:
1 | struct file_t { |
显然是可以的,但是一个unsigned int
类型的成员就占用4字节空间(64位平台),3个成员就占用了12个字节,而实际上我们需要的仅仅是3个bit的标志位!
位段可以一定程度上节省空间,另一方面可以简化对位标志的操作.
C语言提供了位域(或译位段)
来允许我们按位来定义成员并操作.
位域的声明和使用
位域
是一种特殊的结构体,该结构体的成员是signed int
或unsigned int
类型变量中的一组位(bit),我们仍然使用struct
关键字来定义一个位域,并指定每一个成员所占用的位数.
1 | struct permission { |
上述结构体位域有3个位域成员,分别为r,w,x,他们根据定义分别在结构体中占用1bit的空间,共占用3bit空间.
我们可以像访问结构体成员那样来访问各个位段成员,但是需要注意赋的值不能超过位段成员的存储范围,例如3bit(unsigned)的位段成员范围为[0,2^3-1].
例如如下代码说明文件file1的属性为只读,即仅可以读取,不能写入和执行:
1 | struct permission file1; |
位域的兼容性与占用空间
兼容性与实现问题
位段有许多兼容性和实现问题:
- 位段成员如果未显式使用
signed int
或unsigned int
声明,则它究竟被解释为有符号还是无符号数是由编译器决定的. int
被解释为有符号还是无符号也是实现问题.- 位段成员的最大位长度,许多编译器将其限制在整型值的长度内.(注意32位和16位的整型值长度就不一样)
- 位段的成员在内存中的分布情况也是未定义的,取决于具体的实现.
- 当一个位段成员较大,无法放在前面所剩余的位中,编译器可能将其放在下一个内存位置对齐,也可能紧挨在前一个位段成员后面,造成重叠.
- 是否允许除
int
,unsigned int
外类型的位域是未定义的.
第5点即:位域是否能越过分配单元边界,这是未定义的.
占用空间
位域整体是向整型类型对齐的. PS:可能有误
但是由于位域成员的各种基于实现的行为,无法说明位域成员的准确表现.不过我们有2种显式的填充和对齐方法:
使用未命名的字段来填充未命名的空间;使用未命名的0宽度的字段来迫使下一个字段向下一个整数对齐:
1 | struct s{ |
其中f1和f2之间有2bit的空位,f3将存储在(即对齐)下一个unsigned int
的位置处(注意其在这一段内存中的位置仍旧是未定义的).
优缺点
位域的各种用途完全可以使用对应的位操作来替代,但是使用位域显然大大简化了操作—我们将他们视为结构体的成员.
尽管如此,位域在底层仍然无法避免对应的位偏移等操作,它只是对我们屏蔽了而已.
另一方面,位域有着一系列未定义的行为,亦即由实现决定,需要考虑其兼容性问题.
同时,如果需要的标志位的总长度很少,例如上面例子的3bit(r/w/x),可能并不能起到节省空间的作用—例如可以将这3bit的标志位压缩于一个unsigned char
变量中.
总之,在使用位域之前,一定要把位域简化代码的优点和其移植性弱的缺点相权衡!
本章简单讲解了位域
,除了一些底层的代码,一般使用并不多,了解即可.
——WAHAHA 2024.2.24 于火车上完成
开学了QWQ
下一篇:C语言教程-14_4-共用体