C语言位域的最大作用是简化位操作,可以让开发者以直观的方式来操作某一位.如下列定义
struct bs { int a:7; int b:2; int c:1; };
表示用一个整数的前8位表示a,用一个整数的2位表示b,用一个整数的1位的来表示c,
位域定义不能超过数据定义类型的最大位,
如struct {
char a:9; //char 最大值为8位
int b:33; //int 的最大值为32,不能超过其中定义值
}
位域有如下特殊定义,
1) 只要不超过数据定义最大值,多个位域可以定义一个数据单位里,如下是合法,定义,也是常用定义
Struct PC_PIN{
Char bit0:1,
bit1:1,
Bit2:1,
Bit3:1,
Bit4:1,
Bit5:1,
Bit6:1,
Bit7:1;
}
2) 位域可以采用’匿名',定义,这样程度就不能使用这些位,这样定义纯粹是起占位用.
struct foo1 { int a : 1; int : 2; short c : 1; };
上例中,在a和c中有一个2位的匿名占位
struct bs { unsigned a:4 unsigned :0 /*空域*/ unsigned b:4 /*从下一单元开始存放*/ unsigned c:4 }
在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
位域占位计算有点复杂
1. 定义位域不足一个数据位的,按一个完整数据算
Struct tagA{
Int a:3;
Int b;
};
Sizeof()值是 8,因为a仍然按4位来核算.
2. 如果连续定义几点相同类型.而位域总合不超过类型总位数长度的,会被编译器设为一个合并为一个位域定义
如struct tagA{
int a:1;
int b:2;
int c;3
}
等同于 struct tagB{
Int a:1,
b:2,
c:3;
};
Sizeof()的长度都是4,因为tagA的各个成员加起长度都没有超过32,所以仍然为4
3. aaa
位域的被广泛应用于8位单片机编程中.因为一个8位寄存器刚好是一个char 的宽度,因为可以定义一个8个位位域来对寄存器的各个位进行存取.这样程序比较简单并且好理解.但在32位CPU应用反而不广泛,因为32CPU的寄存器是为32位宽度,正好是一个int 的宽度,但int在不同CPU中的表示顺序位是不一致的.在不同字节序CPU里定义的位域,有一些不样,换句话说,定义这样位域需要定义两套类型.如ip的头定义.
以下取自 Linux 中,linux/ip.h
struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u8 ihl:4,
version:4;
#elif defined (__BIG_ENDIAN_BITFIELD)
__u8 version:4,
ihl:4;
#else
#error "Please fix <asm/byteorder.h>"
#endif
__u8 tos;
__u16 tot_len;
__u16 id;
__u16 frag_off;
__u8 ttl;
__u8 protocol;
__u16 check;
__u32 saddr;
__u32 daddr;
/*The options start here. */
};
使用反而不如位操作定义方便,因此32位CPU使用位域有一些麻烦
字节对齐
现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但为为了CPU访问数据的快速,通常都要求数据存放的地址是有一定规律的.比如在32位CPU上,一般要求变量地址都是基于4位,这样可以保证CPU用一次的读写周期就可以读取变量.不按4位对齐,如果变量刚好跨4位的吗,这样需要CPU两个读写周期.效率自然低下.因此,在现代的编译器都会自动把复合数据定义按4位对齐,以保证CPU以最快速度读取,如下例
(gcc version 3.2.2编译器(32位x86平台))
struct A { int a; char b; short c; };
结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个。所以A用到的空间应该是7字节。但是因为编译器要对数据成员在空间上进行对齐。 所以使用sizeof(strcut A)值为8。
现在把该结构体调整成员变量的顺序。
struct B { char b; int a; short c; };
这时候同样是总共7个字节的变量,但是sizeof(struct B)的值却是12,因为b+a 已经超过32,编译无法合并在一个空间,干脆分配了4个byte空间.
但这样分配空间一个不好结果就是,各个成员的相对起移地址,的位移是不确定的.在不同CPU的有不同结果.这样对于经常处理网络包的嵌入式C程序员是一个大问题
如下例,假设某人设计不一不按4位对齐的包协议,如下前一个1 位表示版本号,第2-5位表示,包序号,第6-7位表示端口号.
这时发来一个网络包 {0x01,0x12,0x23,0x10,0x10,0xFF,0xFF}
如果你设计一个结构,去记取
Struct protocol{
Char version;
Int serialno;
Short port;
};
用这个结构去强制读取前面包buffer,会得出一个错误的结果!,包的serial 号和port 号都不会取到 0x10101223和0xFFFF.为什么?因为编译器已经把这个结构优化,造成包结构成员的相对位移发生变化.对应位的值就不是原来的值.
为此,有必要告诉编译器,我这个结构是不需要进行字节对齐优化的.在不同编译器,有不同方法来优化.
一般是采用#pragma pack (n) 编译指令来告诉编译器,后面的结构采用n字节对齐,如果n=1表示,不对齐,强制要原始地址排列.网络包通常需要这样定义
用#pragma pack () 取消上一次字节对齐指令,恢复成缺省对齐编译.
#pragma pack (2) /*指定按2字节对齐*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定对齐,恢复缺省对齐*/
在gcc 还有一种特殊编译属性__attribute__((packed)
,可以强制让结构不对齐,即采用1字节对齐模式.
以下是两种不对齐的方法.
1) 结构内部成员的pack struct foo { char a; int b __attribute__ ((packed)); }; 2) 整个结构的pack struct foo { char a; int b; }__attribute__ ((packed));
文章评论(0条评论)
登录后参与讨论