Cpp位域
内存是以字节为单位进行编址的,编程语言的基本类型中,最小类型的长度一般也就是1个字节。然而,在解决某些问题时,必须要有二进制层面的表达手段,又或者某些情形下根本用不着1个字节,作为强大到令人窒息的C/C++,难道没有解决方法?其提供的完美解决方法就是位域(位段)结构,本文将从定义、说明、内存布局和使用这四个方面对它进行详细的介绍。
1. 位域定义
首先,让我们看一下位域的定义。从定义中可以看出位域本质上其实就是结构体,只不过其成员都是按照特定长度的二进制位进行分配而已。
1 | struct 位域结构体名 |
例如:
1 | struct BitField |
2. 位域说明
其次,我们来看一下位域的一些说明:
\1. 位域可以没有名字的,这时它只用来作填充或调整位置。无名的位域是不能使用的
1 | struct BitFiled_1 |
\2. 宽度为 0 的一个未命名位域强制下一位域对齐到其下一type位域的边界
1 | struct BitFiled_1 |
- 位域的长度不能大于其类型说明符中指定类型的固有长度,比如说int类型的位域长度不能超过32(bit),char的位域长度不能超过8(bit)
1 | struct BitField_2 |
3. 位域内存布局
位域有一个非常重要的用途就是压缩存储,即:能够用1个比特解决的问题,绝不用2个比特。因此,我们非常有必要研究一下其内存布局,这样才能对其压缩存储特性有深入的了解。
整个位域结构体的总大小为最宽基本类型成员大小的整数倍,这一点与常规结构体类型是一致的,从这里也可看出,位域本质上就是结构体;
如果相邻位域字段的类型相同,且其声明的位宽长度之和小于类型的大小(sizeof获取的大小),则后面的位域字段将紧邻前一个字段存储,直到不能容纳为止;
1 | // 假如 BitField_3::a = 0x11,(0001 0001 B); BitField_3::b = 0x2,(10 B); |
- 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的位域字段将从新的存储单元开始,其起始偏移量为类型大小的整数倍;
1 | // 假如 BitField_4::a = 0x1,(0001 B); BitField_4::b = 0x08676665 |
- 如果相邻位域字段的类型不同,则各编译器的具体实现有差异,VC采取不压缩方式
1 | // 假如 BitField_5::a = 0x4, (0100 B); BitField_5::b = 0x5, (0101 B), 则有: |
- 如果位域字段之间穿插着非位域字段,则不进行压缩;
1 | // 假如 BitField_6::a = 0x4,(0100 B); BitField_6::b = 0x65, (0110 0101 B) |
注意:如果不是位域字段之间穿插着非位域字段,如下面这种情况,是进行压缩的:
1 | // 以下为两个字节,可见进行了压缩存储 |
\6. 当使用有符号类型来定义位域,并且无意中使用到了正负(有意或者无意)特性时,就有问题了。
1 | struct BitField_8 |
从其内存布局可以看出,使用位域的最佳实践是:第一,位域的类型要使用无符号类型,并且在整个结构体内部要保持一致;第二,位域的总长度尽量与类型的长度保持一致;第三,不要在两个位域中间穿插非位域字段;如下代码所示:
1 | struct BitFieldDemo |
4. 位域使用
使用以下代码,再结合调试器的内存查看功能,即可清晰的验证本文 位域内存布局 一节所阐述的内容。本文使用编译器和调试器是Windows下的Visual Studio。
1 | // 取位域大小,字节单位 |