1、位段的定义
百度百科中是这样解释位段的:
位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为"位段"或称"位域"( bit field) 。利用位段能够用较少的位数存储数据。
以下,我们均在VS2022的编译环境下去探讨和理解位段
2、位段的声明和使用
A、位段的声明
位段是与结构体相结合的,只有在结构体中才能使用位段,位段使用的根本目的是为了节省不必要消耗的内存空间。
这是位段的声明,这里有4个点需要注意:
1、上图中的1、2、3均是指比特位,即bit,一个二进制位
2、位段的适用类型只限于整型家族
3、同一个结构体中,使用位段时最好保持其中成员类型均相同
4、位段的大小不能超过该类型的实际大小,比如说对于char类型,最多只能是8个比特位,对于int类型,最多只能是32个比特位
B、位段的使用
以下是位段的简单使用:
3、位段的内存大小的计算
考虑到在不同的平台环境下,位段的内存大小计算有所不同,以下关于位段内存大小的计算是基于VS2022的编译环境:
例1:
在位段中,编译器看到char类型便会向内存中申请1个字节的空间。
a占去5个比特位,还剩3个比特位;b将这3个比特位占掉,此时第1个字节的空间用完。
紧接着的成员是char类型的c,又会开辟1个字节的内存空间,c占去4个比特位;
成员d要占去5个比特位,但是之前开辟的1个字节中仅剩4个比特位,不够用,所以将这4个比特位
舍去,重新开辟1个字节用以存储d,故最终该位段的大小为3个字节
例2:
VS编译器看到int类型的成员,便会向内存申请4个字节的空间
a占去1个比特位,还剩31个比特位;b在这剩余的31个比特位中占去30个比特位,还剩1个比特位;
c要占去4个比特位,剩余的1个比特位显然不够用,所以再开辟4个字节的内存空间,在这新开辟的4个字节中,c占到4个比特位
d则是在剩下的28个比特位中占20个比特位
所以,该位段的大小为8个字节.
4、位段在内存中的存储方式
考虑到位段在不同的编译器下存储方式有所不同,以下的讨论均基于VS2022。
探讨位段在内存中的存储方式,其本质是去探讨,在开辟的1个字节或4个字节的空间中,各个结构体成员在该空间内部是从左向右(低地址向高地址)存储的,还是从右向左(高地址向低地址)存储的。
我们这里先说结论:位段在内存中是从右向左存储的,即高地址向低地址存储的
以下进行相关验证:
cpp
struct S
{
char a : 1;//1
char b : 6;//001010
char c : 4;//0101
char d : 3;//011
};
//0001 0101 0011 0101
// 1 5 3 5
int main()
{
struct S s = { 0 };
s.a = 1;//1
s.b = 10;//1010
s.c = 5;//101
s.d = 11;//1011
return 0;
}
将s初始化为0,则s的2个字节,16个比特位中均为0
在s的第1个字节空间中,从右向左存储,将a存储进去,存储的是1;b在初始化后6位二进制中放的是000000,在赋值后,存储的是001010,故第1个字节中存放的是 00010101
在s的第2个字节空间中,从右向左存储,将c存储进去,存储的是0101
由于d只有3个比特位的空间,故赋值11时会发生截断,实际存储到d中的是011,因此第2个字节中存放的是 00110101
将这2个字节转换为16进制表示则为 : 15 35
接下来我们通过内存窗口进行观察验证:
可以看到内存中存储的确实为15 35,说明在VS2022的编译环境下,位段在内存中确实是从右向左(高地址向低地址)存储的
5、位段的局限
位段有很多的局限,这些局限也就导致了位段的跨平台性很差:
1、位段中int类型是看成 signed int 还是 unsigned int 这是不确定的
2、位段中最大位的数目不能确定。位段的大小除了不能超过相应类型的实际大小外,还不能超过机器的位数。在16位的机器上,位段最大为16个比特位;而在32位的机器上,位段最大为32个比特位
3、位段在内存中究竟是从左向右存储,还是从右向左存储,这是标准未定义的
4、在1个结构体中,开辟的内存空间在容纳完1个位段后,剩余的位无法容纳第2个位段时,这些剩余的位是利用还是不利用,这是不确定的
综上所述,为了保证程序的可移植性,应尽量避免位段的使用。