C语言中的位域
定义
struct bit_fields{ /*位域名*/
/*----- 位域列表------- */
/*typename varname:bit_field_length, for example : */
int field1:2;
char field2:3;
uint64_t field3:8;
/* ... */
};
用法
1.无名位域
用来填充和调整位置,硬件寄存器中经常有未使用的bit,可以用无名位域来对应这些bit,例如
typedef struct{
unsigned enable:1;
unsigned :2;
unsigned interrupt_enable:1;
}SPI_CR;
寄存器SPI_CR中enable位和interrupt_enable位中间空了2个bit,用unsigned:2无名位域来填充。
2.不能用来指定位数的类型
如果struct成员是指针变量不能用来指定所占的位数,在64位软件中指针固定站8字节,在32位软件中指针固定占4字节;
在struct成员是double或float类型,也不能指定位数,否则编译出错,位域类型无效。
3.压缩存储规则
a.window vc10编译器:(在vs2010上测试)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
struct type1{
int a:2;
char b:2;
char d:2;
int c:2;
uint64_t e:2;
};
struct type2{
int a:2;
int b:2;
};
struct type3{
int a:2;
uint32_t b:2;
};
int main()
{
printf("sizeof(type1) is %u\n", sizeof(struct type1));
printf("sizeof(type2) is %u\n", sizeof(struct type2));
printf("sizeof(type3) is %u\n", sizeof(struct type3));
system("pause");
return 0;
}
运行结果:
sizeof(type1) is 24
sizeof(type2) is 4
sizeof(type3) is 4
b.linux gcc编译器:(gcc 4.9.2测试)
1.无论相邻字段位域 类型长度是否相等,gcc都采用压缩的处理
2.类型长度相等时,比较简单,基本和vc编译器一样,这里不再赘述;
3.长度不同,首先要满足一条,整个结构体的长度为结构体中最长数据类型长度的整数倍,由于结构体成员的长度不同,每次需要开辟存储空间时开辟的长度与当前成员的类型长度相关,但最终都会以最长数据类型长度对齐,在关键位置放置合适的类型长度可以提高内存使用率,例如以下结构体
struct op_code_t {
unsigned na:4;
unsigned mod:6;
unsigned pr:4;
unsigned re0:6;
unsigned re1:6;
unsigned long immea:22;
unsigned immeb:10;
unsigned nb:6;
};//__attribute__((packed));
将immea声明为unsigned long类型,结构体的长度为8字节,在immea处,前面已经使用了26bit,immea类型长度为8byte,所以第一个存储单元的长度将延长至8byte,immea将占用22bit,总共48bit,小于64bit,不用开辟新的存储单元,immeb类型为unsigned,长度4byte,immeb所在的4字节已经别用掉了48%32= 16bit,剩余空间仍然足够存储10位,所以不用开辟新的存储单元,nb同理,所以最终结构体占8byte。
若在将unsigned long放置在其他任何位置,将immea成员换成unsigned,则整体大小变为16字节,如下:
struct op_code_t {
unsigned na:4;
unsigned mod:6;
unsigned pr:4;
unsigned re0:6;
unsigned long re1:6;
unsigned immea:22;
unsigned immeb:10;
unsigned nb:6;
};//__attribute__((packed));
如上例,首先编译器开辟第一个存储单元,长度为4byte,32bit足够na、mod、pr、re0使用,到re1时,re1类型变为unsigned long类型,第一个存储单元长度延长为8byte,至re1处,共用了26bit,至immea处,存储单元长度又变为sizeof(unsigned),immea所在4字节26%32=26bit,不足以存储immea,所以开辟新的存储单元,长度为4byte,当然,新开辟的存储单元和第一个长度为8byte的存储单元后4byte重合(由此可见,当前使用的存储单元的长度由当前成员变量的类型而定的,并不是一成不变的),immea和immeb正好32bit,刚好用完,到nb,开辟第三块存储单元,长度4byte,只使用了6bit,然后对第三块存储单元进行8byte对齐,最终长度为16byte。
所以规律如下:
gcc计算存储单元的长度和当前成员的类型长度相等,若当前成员所在的空间除去之前成员使用的bit数,足够存储当前成员的bit数,则不开辟新的存储单元,否则开辟长度为当前成员类型长度的新的存储单元,直到结构体末尾,然后按照最长成员类型长度进行对齐。
再举一例,将上述结构体改变如下:
struct op_code_t {
unsigned char na:4;
unsigned mod:6;
unsigned char pr:4;
unsigned re0:6;
unsigned long re1:6;
unsigned immea:22;
unsigned immeb:10;
unsigned nb:6;
};//__attribute__((packed));
第一个存储单元仍为1字节,na使用了4bit,到第二个成员mod时存储单元长度变为4byte,mod使用6bit,加上之前的4bit,总共使用10bit,到pr时,存储单元长度又变为1byte,pr所在的字节,已经被mod用了10%8=2bit,剩余6bit,pr使用4bit,最终剩余2bit;到re0时存储单元长度变为4byte,前面已经使用了4+6+4=14bit,re0使用6bit,至此共使用20bit;到re1处,存储单元长度变为8byte,共使用26bit;至immea存储单元长度变为32bit,已使用26bit,开辟第二块存储单元,长度为4byte,immea存储到第二块存储单元,immeb紧随其后,nb开辟新的存储单元,最后8byte对齐,总共占用16byte。
上例中如果使用gccde _attribute__((packed))属性则,结构体之间无间隙,所有的位将紧挨在一起,共8byte,详见:
http://blog.shengbin.me/posts/gcc-attribute-aligned-and-packed