有些数据在存储时并不需求占用一个完好的字节,只需求占用一个或几个二进制位即可。例如开关只要通电和断电两种形态,用 0 和 1 表现足以,也就是用一个二进位。恰是基于这种思索,C言语又供给了一种叫做位域的数据构造。
在构造体界说时,我们可以指定某个成员变量所占用的二进制位数(Bit),这就是位域。请看下面的例子:
struct bs{ unsigned m; unsigned n: 4; unsigned char ch: 6; }
:前面的数字用来限制成员变量占用的位数。成员 m 没无限制,依据数据类型即可推算出它占用 4 个字节(Byte)的内存。成员 n、ch 被:前面的数字限制,不克不及再依据数据类型盘算长度,它们辨别占用 4、6 位(Bit)的内存。
n、ch 的取值规模十分无限,数据略微大些就会发作溢出,请看下面的例子:
#includeint main(){ struct bs{ unsigned m; unsigned n: 4; unsigned char ch: 6; } a = { 0xad, 0xE, '$'}; //第一次输入 printf("%#x, %#x, %c\n", a.m, a.n, a.ch); //更改值后再次输入 a.m = 0xb8901c; a.n = 0x2d; a.ch = 'z'; printf("%#x, %#x, %c\n", a.m, a.n, a.ch); return 0; }
运转后果:
0xad, 0xe, $
0xb8901c, 0xd, :
关于 n 和 ch,第一次输入的数据是完好的,第二次输入的数据是完整的。
第一次输入时,n、ch 的值辨别是 0xE、0x24('$' 对应的 ASCII 码为 0x24),换算成二进制是 1110、10 0100,都没有超越限制的位数,可以正常输入。
第二次输入时,n、ch 的值变为 0x2d、0x7a('z' 对应的 ASCII 码为 0x7a),换算成二进制辨别是 10 1101、111 1010,都超越了限制的位数。超越局部被直接截去,剩下 1101、11 1010,换算成十六进制为 0xd、0x3a(0x3a 对应的字符是 :)。
C言语规范规则,位域的宽度不克不及超越它所依靠的数据类型的长度。浅显地讲,成员变量多是有类型的,这个类型限制了成员变量的大长度,:前面的数字不克不及超越这个长度。
例如下面的 bs,n 的类型是 unsigned int,长度为 4 个字节,合计 32 位,那么 n 前面的数字就不克不及超越 32;ch 的类型是 unsigned char,长度为 1 个字节,合计 8 位,那么 ch 前面的数字就不克不及超越 8。
我们可以如许以为,位域技巧就是在成员变量所占用的内存当选出一局部位宽来存储数据。
C言语规范还规则,只要无限的几种数据类型可以用于位域。在 ANSI C 中,这几种数据类型是 int、signed int 和 unsigned int(int 默许就是 signed int);到了 C99,_Bool 也被支撑了。
关于C言语规范以及 ANSI C 和 C99 的差别,我们已在VIP教程《C言语的两套规范》中停止了解说。
但编译器在详细完成时都停止了扩大,额定支撑了 char、signed char、unsigned char 以及 enum 类型,所以下面的代码固然不契合C言语规范,但它仍然可以被编译器支撑。
C言语规范并没有规则位域的详细存储方法,分歧的编译器有分歧的完成,但它们都尽量紧缩存储空间。
位域的详细存储规矩如下:
1) 当相邻成员的类型相反时,假如它们的位宽之和小于类型的 sizeof 巨细,那么前面的成员紧邻前一个成员存储,直到不克不及包容为止;假如它们的位宽之和大于类型的 sizeof 巨细,那么前面的成员将重新的存储单位开端,其偏移量为类型巨细的整数倍。
以下面的位域 bs 为例:
#includeint main(){ struct bs{ unsigned m: 6; unsigned n: 12; unsigned p: 4; }; printf("%d\n", sizeof(struct bs)); return 0; }
运转后果:
4
m、n、p 的类型多是 unsigned int,sizeof 的后果为 4 个字节(Byte),也即 32 个位(Bit)。m、n、p 的位宽之和为 6+12+4 = 22,小于 32,所以它们会挨着存储,两头没有裂缝。
sizeof(struct bs) 的巨细之所认为 4,而不是 3,是由于要将内存对齐到 4 个字节,以便进步存取效力,这将在《C言语和内存》专题的《C言语内存对齐,进步寻址效力》一节中具体解说。
假如将成员 m 的位宽改为 22,那么输入后果将会是 8,由于 22+12 = 34,大于 32,n 会重新的地位开端存储,绝对 m 的偏移量是 sizeof(unsigned int),也即 4 个字节。
假如再将成员 p 的位宽也改为 22,那么输入后果将会是 12,三个成员都不会挨着存储。
2) 当相邻成员的类型分歧时,分歧的编译器有分歧的完成计划,GCC 会紧缩存储,而 VC/VS 不会。
请看下面的位域 bs:
#includeint main(){ struct bs{ unsigned m: 12; unsigned char ch: 4; unsigned p: 4; }; printf("%d\n", sizeof(struct bs)); return 0; }
在 GCC 下的运转后果为 4,三个成员挨着存储;在 VC/VS 下的运转后果为 12,三个成员依照各自的类型存储(与不指定位宽时的存储方法相反)。
m 、ch、p 的长度辨别是 4、1、4 个字节,合计占用 9 个字节内存,为什么在 VC/VS 下的输入后果倒是 12 呢?这个疑问将在《C言语和内存》专题的《C言语内存对齐,进步寻址效力》一节中为您解开。
3) 假如成员之间交叉着非位域成员,那么不会停止紧缩。例如关于下面的 bs:
struct bs{ unsigned m: 12; unsigned ch; unsigned p: 4; };
在各个编译器下 sizeof 的后果多是 12。
经过下面的剖析,我们发现位域成员常常不占用完好的字节,有时分也不处于字节的扫尾地位,因而运用&获取位域成员的地址是没有意义的,C言语也制止如许做。地址是字节(Byte)的编号,而不是位(Bit)的编号。
位域成员可以没著名称,只给出数据类型和位宽,如下所示:
struct bs{ int m: 12; int : 20; //该位域成员不克不及运用 int n: 4; };
无名位域普通用来作填充或许调剂成员地位。由于没著名称,无名位域不克不及运用。
下面的例子中,假如没有位宽为 20 的无名成员,m、n 将会挨着存储,sizeof(struct bs) 的后果为 4;有了这 20 位作为填充,m、n 将离开存储,sizeof(struct bs) 的后果为 8。
另外有需要云服务器可以了解下创新互联scvps.cn,海内外云服务器15元起步,三天无理由+7*72小时售后在线,公司持有idc许可证,提供“云服务器、裸金属服务器、高防服务器、香港服务器、美国服务器、虚拟主机、免备案服务器”等云主机租用服务以及企业上云的综合解决方案,具有“安全稳定、简单易用、服务可用性高、性价比高”等特点与优势,专为企业上云打造定制,能够满足用户丰富、多元化的应用场景需求。