struct位域
摘要: 作者介绍了C++中结构体位域的定义、存储方式、大小计算规则及其与系统端序的关系,强调位域占用空间总是其最大成员类型的整数倍,且不能跨越两个标识符,实际存取通过移位和按位与实现,理解其底层实现有助于设计高效的调试和属性设置方案。 (评价: B)
struct位域
位域可以将成员变量拆分成bit的粒度. 用法一般是:
|
|
例如以下:
|
|
BITS
的成员d1
/d2
/d3
/d4
各占4bits, 最后还有16bit的保留位. 所以sizeof(BITS)
的大小是4.
稍微改动一下, 去掉16bit的保留位:
|
|
现在BITS
的大小是2吗? 不是的, sizeof(BITS)
还是4.
再改动, 保留20bit空间:
|
|
现在BITS
的大小是4吗? 不是的, sizeof(BITS)
是8.
以上, BITS
声明的位域数和不足uint32_t
占位时, BITS
占位是sizeof(uint32_t)
, 超过时, 这是sizeof(uint32_t)
的整数倍. 这一点和struct
保持一致.
再来改一笔, 把uint32_t
改成uint8_t
:
|
|
现在BITS
的大小是4吗? 不是的, sizeof(BITS)
是2.
如果把某个uint8_t
改成uint16_t
, sizeof(BITS)
依然是2, 如果改成uint32_t
, 则sizeof(BITS)
是4.
有以下结论:
struct位域占用的大小总是其最大标识符的整数倍.
我们可以单独写入或者读出每个成员:
|
|
如果成员比较多则比较麻烦, 这时候可以使用union.
union初始化位域
|
|
我们可以直接初始化所有位域:
|
|
也可以通过data
一次性设置所有位域:
|
|
但是data
和d1-d4
的对应关系如何? 则需要考虑系统的小大端.
小大端模式
小端: 低位Byte存低地址, 高位Byte存高地址;
大端: 低位Byte存高地址, 高位Byte存低地址;
可用以下代码判断:
|
|
如果是小端, 则small为true, 否则small为false.
如果是小端存储:
|
|
输出d1-d4
则是:
|
|
如果是大端存储, 输出d1-d4
则是:
|
|
了解小大端存储方式对开发有一定的帮助, 比如下面一个例子:
设计一个debug接口, 用户set一个属性, 系统通过获取这个属性可以支持不同的debug模式, 但是因为某些原因, 只允许设置一个属性, 值是32位.
此时我们就可以用上面的BITS
. 获取prop
:
|
|
仅用一个prop
, 就可以支持同时设置多个debug属性.
我们来看一个例子:
|
|
在小端存储系统中, 输出d1-d4
是:
|
|
我们改动一下:
|
|
预计输出是:
|
|
但是实际输出依然是:
|
|
原因是d1
/d2
已经占用6bit, 再加d3
是10bit超过了uint8_t
的8bit, 所以d1
/d2
补齐2bit按照8bit对齐.
我们可以在改动一下验证这个结论:
|
|
现在d1-d4
的输出是:
|
|
符合预期d1-d3
正好占8bit, 所以不会有补齐对齐操作.
位域如何实现的
通过编译器翻译后的汇编代码, 我们可以基本知道其原理:
|
|
汇编后:
将值0x87654321赋值给bits.data
, 这里比较好理解.
|
|
接下来时获取d1
的值:
|
|
从首地址拿数据, 然后与0xFF(15)按位与.
再获取d2
的数据:
|
|
与d1
的区别在于, 右移4bit, 然后与0x3按位与, 这时是提取2bit.
再获取d3
:
|
|
有点不同, 为什么没有按位与的操作了? 因为这里是取的BYTE, 右移6bit就可以得到高位的2bit了.
d4
则和d1
类似, 只不过取值地址需要+1:
|
|
所以, 位域操作在逻辑上和位操作是类似的, 也是通过移位和与或运算得到.
结论
综上, 总结struct位域:
- struct大小是最大标识符的整数倍
- union赋值struct位域需要考虑小大端
- 位域不能横跨两个标识符, 此时需要补齐对齐
- 位域也是通过移位和与或运算得到