看到本站上面关于位域的博客不少,但是真正说得清楚的、说得对的却好像真没有,本人在查阅了一些较为官方的文件之后,发表一下对于位域的解释。
位域
声明具有以位为单位的明确大小的类数据成员。相邻的位域成员可以打包成共享和跨过各个字节。
1. 语法
declarator:constant-expression
declarator
是在程序中访问成员的名称。 它必须是整型类型(包括枚举类型)。
constant-expression
指定结构中成员所占据的位数。 匿名位字段(即没有标识符的位字段成员)可用于填充。
Note:宽度为 0 的未命名位域强制将下一个位域与下一个类型边界对齐,其中类型是成员的类型。
2. 解释
关于上述的语法解释如下:
struct S
{
uint32_t a : 7; // 变量a占据uint32_t中的连续7个bit
uint32_t b : 3; // 变量b占据uint32_t中的连续3个bit
uint32_t : 4; // 占位uint32_t中的连续4个bit(但不能使用)
uint32_t : 0; // 占位所在的uint32_t中剩下的所有bit(但不能使用)
uint32_t c : 0; // 不符合语法规则,位数为0必须使用无名位域
};
3. 占位
占位规则如下:
- 当相邻成员的类型相同时,如果它们的位宽之和小于类型的
sizeof
大小,那么后面的成员紧邻前一个成员存储,直到不能容纳为止;如果它们的位宽之和大于类型的sizeof
大小,那么后面的成员将从新的存储单元开始。 - 无名位域如果是0占位,则以所在的类型大小内全部占位;如果是非0占位,和有名位域占位一样,只是不能使用。
上面说的是什么意思呢?
解释一下:
struct S1
{
uint16_t a : 7; // 变量a占据uint16_t中的连续7个bit
uint16_t b : 3; // 变量b占据uint16_t中的连续3个bit
uint16_t c : 5; // 变量c占据uint16_t中的连续5个bit
/*
根据第一个规则的前半句话:
现在的类型相同且相邻,所有的位宽之和为7+3+5=15,小于类型的sizeof(uint16_t)=16。
因此会直接都紧密相接,在内存中的状态如下:
|* * * *|* * * *|* * * *|* * * *|
|a a a a|a a a b|b b c c|c c c *|
所以sizeof(S1)=2
*/
};
struct S2
{
uint16_t a : 7; // 变量a占据uint16_t中的连续7个bit
uint16_t b : 3; // 变量b占据uint16_t中的连续3个bit
uint16_t c : 8; // 变量c占据uint16_t中的连续8个bit
/*
根据第一个规则的后半句话:
现在的类型相同且相邻,所有的位宽之和为7+3+8=18,大于类型的sizeof(uint16_t)=16,
因此会尽量向前靠齐,而c会从类型的下一个开始,且紧密相接,在内存中的状态如下:
|* * * *|* * * *|* * * *|* * * *||* * * *|* * * *|* * * *|* * * *|
|a a a a|a a a b|b b * *|* * * *||c c c c|c c c c|* * * *|* * * *|
所以sizeof(S2)=4
*/
};
struct S3
{
uint16_t a : 7; // 变量a占据uint16_t中的连续7个bit
uint16_t : 0; // 剩下全部占位
uint16_t c : 8; // 变量c占据uint16_t中的连续8个bit
/*
根据第二个规则的前半句话:
现在的类型相同且相邻,从a开始是7个bit;到下一个位数是0,表示剩下全部占位,
因为第8bit是在上一个变量a的同一个uint16_t内,所以剩下的16-7=9个bit全部占位;
c从新的一个uint16_t开始,在内存中的状态如下:
|* * * *|* * * *|* * * *|* * * *||* * * *|* * * *|* * * *|* * * *|
|a a a a|a a a *|* * * *|* * * *||c c c c|c c c c|* * * *|* * * *|
所以sizeof(S3)=4
*/
};
struct S4
{
uint16_t a : 7; // 变量a占据uint16_t中的连续7个bit
uint16_t : 5; // 往后占位7个bit
uint16_t b : 2; // 变量b占据uint16_t中的连续2个bit
uint16_t c : 8; // 变量c占据uint16_t中的连续8个bit
/*
根据第二个规则的后半句话:
现在的类型相同且相邻,从a开始是7个bit;到下一个位数是5,表示占用接下来的5个bit;
b的位数是2位,因为7+5+2=14,一个uint16_t够用,所以紧接着排列;
c的位数是8位,因为7+5+2+8=22,不够一个uint16_t来使用,
因此会从新的一个uint16_t开始,在内存中的状态如下:
|* * * *|* * * *|* * * *|* * * *||* * * *|* * * *|* * * *|* * * *|
|a a a a|a a a *|* * * *|b b * *||c c c c|c c c c|* * * *|* * * *|
所以sizeof(S4)=4
*/
};
参考:
- https://zh.cppreference.com/w/cpp/language/bit_field
- https://learn.microsoft.com/zh-cn/cpp/cpp/cpp-bit-fields?view=msvc-170