位域(位段)用法,对齐机制
1. 什么是位域
位域
:有些数据在存储时并不需要占用一个完整的字节,只需要占用一个或几个二进制位即可。为了充分利用好内存空间,C语言又提供了一种叫做位域
的数据结构- 用法:
- 在结构体定义时,指定某个成员变量所
占用的二进制位数
(Bit) :
后面的数字用来限定成员变量占用的位数
- 在结构体定义时,指定某个成员变量所
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint8_t value_1 : 1;
uint8_t value_2 : 3;
uint8_t value_3 : 4;
}BitUnion;
int main() {
BitUnion data;
data.value_1 = 1;
data.value_2 = 3;
data.value_3 = 10;
printf("data = 0x%x, sizeof_data = %d", data, sizeof(data));
return 0;
}
如上示例所示:value_1、value_2、value_3分别使用1个bit位、3个bit位、4个bit位,共使用了8个bit即一个字节
打印结果位为 :data = 0xa7, sizeof_data = 1
2. 位域的对齐机制
- 位域基本概念十分简单,但使用中经常出现问题,首先是
内存对齐机制
示例1:
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint8_t value_1 : 4;
uint8_t value_2 : 8;
uint8_t value_3 : 4;
}BitUnion;
typedef struct {
uint8_t value_1 : 4;
uint8_t value_2 : 4;
uint8_t value_3 : 8;
}BitUnion1;
int main() {
BitUnion data;
BitUnion1 data1;
memset(&data, 0 ,sizeof(data));
memset(&data1, 0, sizeof(data1));
data.value_1 = 0xf;
data.value_2 = 0xff;
data.value_3 = 0xf;
uint8_t* P_data = (uint8_t*)&data;
for (uint8_t i = 0; i < sizeof(data); i++) {
printf("data[%d] = 0x%x\r\n", i, P_data[i]);
}
data1.value_1 = 0xf;
data1.value_2 = 0xf;
data1.value_3 = 0xff;
uint8_t* P_data1 = (uint8_t*)&data1;
for (uint8_t i = 0; i < sizeof(data1); i++) {
printf("data[%d] = 0x%x\r\n", i, P_data1[i]);
}
return 0;
}
打印结果:
如上示例中 :BitUnion的value_1、value_2、value_3分别使用4个bit位、8个bit位、4个bit位,加起来共16个bit即2个字节,但是实际中使用了3个byte;因为value_1占用了4个bit,剩余的4个bit明显无法存储8个bit的value_2,所以value_1将从新的存储单元开始;
但是BitUnion1的value_1、value_2、value_3分别使用4个bit位、4个bit位、8个bit位,加起来共16个bit即2个字节,实际也只是使用了2个byte。
- 上示例
BitUnion
与预期设计有误的原因是由位域的具体存储规则
导致的:- 当
相邻成员的类型相同
时,如果它们的位宽之和小于类型的 sizeof 大小
,那么后面的成员紧邻前一个成员存储
,直到不能容纳为止; - 如果它们的
位宽之和大于类型的 sizeof 大小
,那么后面的成员将从新的存储单元开始
,其偏移量为类型大小的整数倍。
- 当
示例2:
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint8_t value_1 : 8;
uint16_t value_2 : 8;
uint8_t value_3 : 8;
}BitUnion;
typedef struct {
uint8_t value_1 : 8;
uint16_t value_2 : 8;
uint16_t value_3 : 8;
}BitUnion1;
int main() {
BitUnion data;
BitUnion1 data1;
memset(&data, 0 ,sizeof(data));
memset(&data1, 0, sizeof(data1));
data.value_1 = 0xff;
data.value_2 = 0xff;
data.value_3 = 0xff;
uint8_t* P_data = (uint8_t*)&data;
for (uint8_t i = 0; i < sizeof(data); i++) {
printf("data[%d] = 0x%x\r\n", i, P_data[i]);
}
data1.value_1 = 0xff;
data1.value_2 = 0xff;
data1.value_3 = 0xff;
uint8_t* P_data1 = (uint8_t*)&data1;
for (uint8_t i = 0; i < sizeof(data1); i++) {
printf("data[%d] = 0x%x\r\n", i, P_data1[i]);
}
return 0;
}
打印结果:
BitUnion中,value_2定义成uint16_t、value_3定义成uint8_t ,两者的数据类型不一样,即使有足够的存储空间也不会拼接在一起
- 因此只有
相邻成员的类型相同
,位段才会考虑拼接使用
说明:
上示例中可能会出现如下结果:编译是将uint8_t转成了uint16_t,产生原因是内存对齐机制导致
- 内存对齐机制
内存对齐机制
:计算机内存是以字节(Byte)为单位划分的,理论上CPU可以访问任意编号的字节,但实际情况并非如此。CPU 通过地址总线来访问内存,一次能处理几个字节的数据,就命令地址总线读取几个字节的数据。32 位的 CPU 一次可以处理4个字节的数据,那么每次就从内存读取4个字节的数据;少了浪费主频,多了没有用。64位的处理器也是这个道理,每次读取8个字节。- 对于程序来说,一个
变量最好位于一个寻址步长的范围内
,这样一次就可以读取到变量的值;如果跨步长存储,就需要读取两次,然后再拼接数据,效率显然降低了。 - 内存对齐虽然和硬件有关,但是
决定对齐方式的是编译器
,可以通过编译器参数修改,以VS stdio为例,更改对齐方式的步骤为:项目 --> 属性 --> C/C++ --> 代码生成 --> 结构成员对齐
3. 位域的赋值
- 如果给位域变量赋值超过了其范围,会产生什么结果:
- 示例如下:
#include <stdio.h>
#include <stdint.h>
typedef struct {
uint8_t value_1 : 1;
uint8_t value_2 : 3;
uint8_t value_3 : 4;
}BitUnion;
int main() {
BitUnion data;
memset(&data, 0, sizeof(data));
data.value_1 = 2; // 0000 0010
data.value_2 = 9; // 0000 1001
data.value_3 = 0xA1; // 1010 0001
// value_3取4位 0001, value_2取3位001, value_1取1位0 -> 00010010
uint8_t* P_data = (uint8_t*)&data;
for (uint8_t i = 0; i < sizeof(data); i++) {
printf("data[%d] = 0x%x\r\n", i, P_data[i]);
}
return 0;
}
打印 :data[0] = 0x12
- 当位域变量赋值时,只会去
匹配bit位相同段的2进制数数值
,即变量value占n个bit位,则只会从低位开始取赋值值的n个bit位上的值
感谢阅读 若有错误 敬请见谅!!!