在一般学习C语言中,关于位域的知识点以及讲解一般都很少,导致我第一次接触到的时候一片迷茫,在嵌入式系统以及相关单片系统学习使用方面会比较多所以在此对位域的详解及使用做一个总结(个人理解不晦涩)
在位域理解前需检查当前系统的模式(大端模式和小端模式)(一般系统都是小端系统,不确定的可以用代码check一下)。在使用位域前需要特别注意位域的对齐方式LSB以及MSB可以理解为对应大小端。后面会详解。
一般在对内存要求比较高的地方会用到,定义的方式类似于结构体,区别在其成员后面加上 成员 :[bit-length]; 里面对应的数字就是定义的位数,即数字越大可用的位数越多(当然不能超过本身数据类型的最大字节数),数字越小可用位数越少(需注意有符号数据类型需保证有一个符号位,例如int 类型最少定义为 bit-length = 2,如果为无符号类型,那可以定义为1位,许多应用中就会这么用,例如unsugned int xx :1)。
1)位域总是从字的第一个位开始;(这个不难理解,要不然你的内存就没法去访问它了)
2)可以给未命名的域声明大小;(例如 unsigned : bit-length 这种域可以在字段中提供填充值)
3)不能使用位域变量的地址;(意味着不能对该变量取地址操作,如scanf函数,但可以通过中间变量赋值,如 scanf(&t) 再将t读入位域)
4)字段中可以有未使用的位;(后面会讲到内存管理)
5)位域不能数组化;(不能定义一个位域数组,但其成员可以是数组)
6)位域必须进行赋值,且不要超过其位的限制;(否则数据问题会很大)
7)再同一个位域中最好不要包含其他的数据类型
#include
#include
typedef struct {
uint8_t a : 1;
uint8_t b : 3;
uint8_t c : 4;
}Bit;
int main() {
Bit data;
data.a = 1;
data.b = 7;
data.c = 10;
printf("data = %#x, sizeof_data = %d", data, sizeof(data));
return 0;
}
上述结果
能发现其只占用了一个字节,即1+3+4=8bit 只占一个字节,大大节省了内存空间。这就是位域的实际效能。
如果其赋值超过了他的位呢?
上述代码,将 data.b 的值改为10 呢,他会打印出什么值?
结果会出乎意料:
将会得到 2 ;这就是小端模式的结果;
即 10 的二进制为 1010,但 data.b 的位数只有3位,LSB对齐方式将会从低位开始取3位,即data.b
即 010 改为二进制即 2 ,想想如果是MSB会打印出什么;
还是上述代码,稍作修改
#include
#include
typedef struct {
uint8_t a : 4;
uint8_t b : 4;
uint8_t c : 5;
}Bit;
int main() {
Bit data;
data.a = 1;
data.b = 10;
data.c = 10;
printf("data.b = %d",data.b);
printf("data = %#x, sizeof_data = %d\n", data, sizeof(data));
return 0;
}
将其位数修改为 4 4 5结果应该符合预期:2个字节
但将其顺序稍作修改 改为 4 5 4,结果就会得到不同的值:3个字节
得到相关结论: 在相邻成员同类型的情况下,若果他们的位长之和小于该类型的sizeof大小,那么后面的成员将会紧接着上一个成员存储,知道不能容纳为止。
如果大于其类型的sizeof,后面的成员将会从新的存储单元开始,其偏移量为类型大小的整数倍。
所以第一次, a 与 b 处于同一字节中,c存放于下一字节中,所以总占空间为2个字节。 在第二次中,a 为 4个位 与 b 的 5 个位加为 9 大于其8 字节数,所以另起一个字节存放,c 的位 为 4 所以还是不能与 b 一起存放,所以再起一个字节存放,所以总共占了 3 个字节。
不同的编译器会有不同的结果,最开始我也是百思不得其解,VC6采用的不压缩方式,即不同类型位于字段放在不同的位域类型字节中,即从新的存储单元开始。而gcc Dev-C++都采用压缩方式,即可以放一起
如图:代码示例:(gcc编译器)
结果为:
即占两个字节,4+7+5 = 16
用结构体位域存储CAN数据格式的轮速数据。