一、位域的形式
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。
注:因编译平台的不同,数据类型的长度也可能有异,以下int代表16Bit型(如果是32Bit类似,具体分析)。
1、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度
- 基本概念
- struct _M
- {
- (1) 类型 参数名 : 占位大小;
(2) 类型 : 占位大小;
}
(1)类型 -- int,unsigned(32位),short,char。
参数名 -- 同个结构体里面不能重名参数。
占位大小 -- 不能大于类型最最大位数。
(2) 一般用于寄存器中保留位
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。
对于位域的定义尚有以下说明:
(1)、一个位域必须存储在同一个字节中,不能跨两个成员变量类型。如一个成员变量所剩空间不够存放另一位域时,应从下一成员变量起存放该位域。也可以有意使某位域从下一成员变量开始。
例如:
struct bs
{
int a:4;
int :0;
int b:4;
int c:4;
}
在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
2. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
struct test
{
unsigned char a:1;
unsigned char :2;
unsigned char b :3;
unsigned char c :2;
};
从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。
二、位域的使用
位域的使用和结构成员的使用相同,其一般形式为: 位域变量名.位域名,位域允许用各种格式输出。
main()
{
struct bs
{
unsigned char a:1;
unsigned char b:3;
unsigned char c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d,%d,%d ",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d,%d,%d ",pbit->a,pbit->b,pbit->c);
}
上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。程序的9、10、11三行分别给三个位域赋值。(有人说,赋值不能超过该位域的允许范围,这个目前我还没测会导致后果,大家可以试试)程序第12行以整型量格式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针 方式给位域a重新赋值,赋为0。第15行使用了复合的位运算符"&=", 该行相当于: pbit->b=pbit->b&3位域b中原有值为7,与3作按位与运算的结果为3(111&011=011,十进制值为 3)。同样,程序第16行中使用了复合位运算"|=", 相当于: pbit->c=pbit->c|1其结果为15。程序第17行用指针方式输出了这三个域的值。
为了节省空间,可以把几个数据压缩到少数的几个类型空间上,比如需要表示二个3位二进制的数,一个2位二进制的数,则可以用一个8位的字符表示之。
三、汇总总结
1、占位大小问题。
2、字节对齐问题。
3、特殊保留位0。
4、该结构体在内存中存放位置。
- 占位大小问题:
- err -- int类型是32位,34大于32位,所以编译出错。
struct _A {
int x1:34;
};
err -- char类型是8位,9大于8位,所以编译出错。
Struct _A1
{
char x1:9;
};
字节对齐
偏移量 -- 类型和占位大小影响(与结构体字节对齐差不多意思)
struct B
{
char x1:1; //1位,字节对齐占1字节,8位。
char x2:8;
char x3:1;
char x4:8;
char x5:1;
- };
- struct C
- {
char x1:4;
char :1;
char x2:3;
- };
- struct _C
- {
- char x1:4;
char :8;
char x2:3;
- };
- 特殊保留位0
- struct C1
- {
char x1:4;
char :0; //这个0占了4个字节
char x2:3;
- };
- struct_C1
- {
- char x1:4;
char :4; //这个0占了4个字节
char x2:3;
};
内存中存放顺序
struct D
{
char x1:1; //最低位
char x2:1;
char x3:1;
- char x4:1;
- char x5:1;
- char x6:1;
- char x7:1;
- char x8:1; //最高位
- };
- int main()
- {
- //字节对齐
B b;
printf("sizeof(b) = %d\n", sizeof(b));
C c;
printf("sizeof(c) = %d\n", sizeof(c));
_C _c;
printf("sizeof(_c) = %d\n", sizeof(_c));
//特殊保留位0
C1 c1;
printf("sizeof(c1) = %d\n", sizeof(c1));
memset(&c1, 0, sizeof(c1) );
c1.x1 = 0xf;
c1.x2 = 0x7;
printf("c1 = 0x%x\n", c1);
_C1 _c1;
printf("sizeof(_c1) = %d\n", sizeof(_c1));
memset(&_c1, 0, sizeof(_c1) );
_c1.x1 = 0xf;
_c1.x2 = 0x7;
printf("_c1 = 0x%x\n", _c1);
- //内存中存放顺序
D d;
memset(&d, 0, sizeof(d) );
printf("d = 0xx\n", d);
d.x1 = 1;
printf("d = 0xx\n", d);
}
四、例子
- 在使用结构体位制的时候有两点要特别注意:
- 1.//位段成员的类型仅能够为unsigned或者int
- 2. unsigned b:4;
- unsigned :0; //定义长度为0的位段时不能指定名字,否则编译不过
unsigned d:1; //定义了0字段后,紧接着的下一个成员从下一个存储单元开始存放; 此例子中,d前面那个存储单元中的余下的27位中被0填充了
- #include
typedef struct _A
{
- unsigned int a:4; //位段成员的类型仅能够为unsigned或者int
- unsigned b:4;
- unsigned c:2;
- unsigned d:6;
- unsigned E:1;
- unsigned D:2;
- unsigned T:3;
- unsigned A:9;
- unsigned h:4; //前面已经为31,故4+31>32已超过一个存储单元,所以4在一个新的存储单元存放
unsigned y:29;//由于前面的4在一个新的存储单元的开头存放,且29+4>32, 故在另一个新的存储单元存放
}A; //所以最后求出的A的大小是4 + 4 + 4 =12
/*对上面的具体解释: 一个位段必须存储在同一个存储单元中,不能跨两个单元.如果某存储单元空间中不能容纳
下一个位段,则改空间不用,而从下一个存储单元起存放该位段. 结构体A中的h和y就是这种情况.在gcc环境下,测试后,一个存储单元为4个字节.
typedef
struct _S
- {
- unsigned a:4;
- unsigned b:4;
- unsigned c:22;
- unsigned q:1;
- unsigned h:1;
- //unsigned i:33; // 错误:‘i’ 的宽度超过它自身的类型
- //unsigned i:1;当多出此行时,该结构体大小由4变为8,因为此行之前正好为32位
} S;
typedef struct _T
- { //当没有占满一个存储单元时,结构体的大小对齐为一个存储单元的大小
unsigned a:2;
- unsigned b:2;
- unsigned j:1;
- unsigned : 1;//可以定义无名位段,此例中该无名位段占用1位的空间,该空间将不被使用
} T;
typedef struct _V
- {
- unsigned a:1;
- unsigned b:4;
- unsigned :0; //定义长度为0的位段时不能指定名字,否则编译不过
- unsigned d:1; //定义了0字段后,紧接着的下一个成员从下一个存储单元开始存放;
- }V; //此例子中,d前面那个存储单元中的余下的27位中被0填充了
int main()
- {
- A a; S s; T t; V v;
- printf("sizeof(a)=%d\n", sizeof(a));
- printf("sizeof(s)=%u\nsizeof(t)=%u\n", sizeof(s), sizeof(t));
- printf("sizeof(v)=%d\n", sizeof(v));
- return 0;
- }