位域(位段)用法,对齐机制

位域(位段)用法,对齐机制

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位上的值

感谢阅读 若有错误 敬请见谅!!!


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

园长QwQ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值