原创 C的位域(bit fields )

2010-6-26 09:58 2823 9 9 分类: MCU/ 嵌入式

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条评论)

登录后参与讨论
我要评论
0
9
关闭 站长推荐上一条 /2 下一条