位域

来源:互联网 发布:pwp网络用语是什么意思 编辑:程序博客网 时间:2024/05/21 09:00

1. 位域的定义

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有01两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为位域位段。所谓位域是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:

位结构定义的一般形式为:
struct 
位结构名{
   
数据类型 [变量名]:整型常数;   //成员称为位域或者位段
   
数据类型 [变量名]:整型常数;
}
位结构变量;

其中:数据类型必须是整型(包括字符型)整型常数的范围是数据类型的长度,如定义为short,则范围是1~16,也就是说,当定义一个short类型的成员位段时,它的整形常数取值范围必须在0-16之间,超出范围编译器将报语法错。具体范围如下:

类型

整形常数范围

char, unsigned char

1-8

short, unsigned short

1-16

int, unsigned

1-32

long, unsigned long

1-32

当然上面的范围值也可以是0,但是作用不同,后面会提到。
例如:

struct webpage{
    unsigned char incon: 8;      // incon
占用低字节的0~78
    unsigned char txcolor: 4;   // txcolor
占用高字节的0~3位共4
    unsigned char bgcolor: 3;  // bgcolor
占用高字节的4~6位共3
    unsigned char blink: 1;      // blink
占用高字节的第7
}ch;
printf("%d\n", sizeof(struct webpage));

输出结果:2

 

2. 位域的对齐与计算

上面结构体大小为2,但是,为什么会是2呢?先来了解下,结构体的对齐原则:

1). 若相邻成员变量类型相同,且其位宽之和不大于成员变量类型位宽(在此严重强调,是类型位宽而不是成员变量sizeof,也不是类型的sizeof或者其他什么)大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2).
如果相邻位域字段的类型相同,但其位宽之和不大于成员变量的类型宽度大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3).
如果相邻的位域字段的类型不同,不同位域字段存放在不同的位域类型字节中(这一条其实会根据编译器的不同而采用不同的规则)

例如:

struct A
{
    char c1:1;
    char c2:3;
    unsigned short s2:13;
    unsigned long i:3;
};

printf(“%d\n”, sizeof(struct A));

我们先分析下,如果遵循上面的对齐原则,结果应该会是多少。

c1, c2符合原则2,类型相同,那么就是 c11位,在0上;c23位在1~3上,因为后面的unsigned shortc1, c2不是同类型,符合原则3,所以存储在新的字节上。即c1, c2在一个字节上; s213位,在0-12上,也因和后面的i不是同类型,所以存储在新字节上,即s2占两个字节。i3位,在0-2上,占四个字节。

即:1 + 2 + 4 = 7,但实际上,结构体还有个原则要遵守,即:和结构体一样,位结构体也是按照成员的最大长度字节来对齐分配空间的。如顺序读到的位域中最长的那个字节数对齐,如为4字节的long,则按4字节对齐。若最长仅为char,则按char对齐。即依次按读取到的数据类型进行对齐,并计算出最后的sizeof()!所以,这里最大的成员类型是4个字节,结构体的大小会是它的倍数,所以为距7最小4的倍数是8。得出,上面位结构体大小为:8VC 6.0下输出结果也为8

但是,在Code::Blocks 10.5中,输出结果却为4。也就是说,它并没有遵循上面的原则3。所以,原则3会和具体的编译环境有关。

 

如果位域上的整形范围值是0,则下个位域从新的字节开始(即使是同类型的位域),如:

struct bs
{
    unsigned a:4;
    unsigned :0;      //
空域
    unsigned b:4;    //
从新字节开始存放
    unsigned c:4;
} ;

上面这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从新的字节开始,占用4位,c占用4位。上面位结构体大小为:8

 

3. 位域的访问

位结构成员的访问与结构成员的访问相同。例如:访问下例位结构中的bgcolor成员可写成ch.bgcolor进行访问:
struct webpage{
    unsigned char incon: 8;      // incon
占用低字节的0~78
    unsigned char txcolor: 4;   // txcolor
占用高字节的0~3位共4
    unsigned char bgcolor: 3;  // bgcolor
占用高字节的4~6位共3
    unsigned char blink: 1;      // blink
占用高字节的第7
}ch;

位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:

struct k
{
    int a:1;
    int :2;    //
2位不能使用
    int b:3;
    int c:2;
};

 

注意:
1.
一个位域必须存储在定义它的一个数据类型内部,不能跨跨该数据类型。如char定义的位域所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
2.
由于位域不允许越过定义它的数据类型,因此位域的长度不能大于定义它的数据类型的长度。
3.
位结构总长度(位数),是各个位成员定义的位数之和再向最大结构成员对齐。
4.
位结构成员可以与其它结构成员一起使用。
例如:
struct info{
char name[8];
int age;
struct addr address;
float pay;
unsigned char state: 1;
unsigned char pay: 1;
}workers;
上例的结构定义了关于一个工人的信息。其中有两个位结构成员,每个位结构成员只有一位,unsigned char数据类型,因此只占一个字节但保存了两个信息,该字节中第一位表示工人的状态,第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。

 

这里有篇文章也写的不错http://blog.csdn.net/juliababy/article/details/2873796

=====================================================================================

一、首先说概念:

位结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构比按位运算符更加方便。
位结构定义的一般形式为:
struct  位结构名{
数据类型 [变量名]: 整型常数; //成员称为“位域”或者“位段”
数据类型 [变量名]: 整型常数;
} 位结构变量;
其中: 数据类型必须是整型(int/char/short)。 整型常数的范围是数据类型的长度, 如定义为short,则范围是1~16。
变量名是选择项, 可以不命名, 这样规定是为了排列需要。
例如: 下面定义了一个位结构。
struct webpage{
unsigned char incon: 8; /*incon占用低字节的0~7共8位*/
unsigned char txcolor: 4;/*txcolor占用高字节的0~3位共4位*/
unsigned char bgcolor: 3;/*bgcolor占用高字节的4~6位共3位*/
unsigned char blink: 1; /*blink占用高字节的第7位*/
}ch;
printf("%d/n",sizeof(struct webpage));输出:2。
位结构成员的访问与结构成员的访问相同。
例如: 访问上例位结构中的bgcolor成员可写成:
ch.bgcolor

注意:
1. 一个位域必须存储在定义它的一个数据类型内部,不能跨跨该数据类型。如char定义的位域所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。
2.由于位域不允许越过定义它的数据类型,因此位域的长度不能大于定义它的数据类型的长度。
3. 位结构总长度(位数), 是各个位成员定义的位数之和再向最大结构成员对齐。
4. 位结构成员可以与其它结构成员一起使用。
例如:
struct info{
char name[8];
int age;
struct addr address;
float pay;
unsigned char state: 1;
unsigned char pay: 1;
}workers;
上例的结构定义了关于一个工人的信息。其中有两个位结构成员, 每个位结构成员只有一位, 用unsigned char数据类型,因此只占一个字节但保存了两个信息, 该字节中第一位表示工人的状态, 第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。

二、再说说位结构的位域存储顺序问题

我们知道字节存储顺序有高字节优先的big-endian大端存储法(高字节数据放在低字节地址处)和低字节优先的little-endian小端存储法,无论使用大端法还是小端法,都不存在技术原因,只是涉及到处理器厂商的立场和习惯。INTEL的X86平台使用小端法,IBM、Motorola、Sun Microsystem的大多数微处理器则使用大端法,还有部分微处理器可以由用户自己设置是使用大端法还是小端法,如ARM、MIPS、PowerPC等。
位域在存储时的顺序和它的编译器有关,一般是先申请的放在低位。程序举例如下:

小端???

#include "stdio.h"
void main()
{
 union
 {
 struct student
 {
  unsigned char s1:1;
   unsigned char s2:3;
 }x;
 unsigned char c;
 }v;
 v.c=0;
 v.x.s1=0;
 v.x.s2=4;
printf("%d/n",v.c);
printf("%d/n",sizeof(struct student)); 
}
输出:
8
1

即结构体成员申请是按照顺序从低地址开始。所以上边结构体v在内存中数据的排列顺序为

    s1 s2
    |0| 0|0|1| 0|0|0|0| (1个字节,因为是unsigned char类型)
低地址       高地址


s1放着0
s2放着4(二进制100),在内存里由低到高为“|0|0|1|”。
所以v.c为二进制00001000,即十进制8。
同时,因为s1占一个位,s2占三个位,而两者都是unsigned char型,且最大的数据类型也就是unsigned char型,一个字节足够放下s1和s2了。所以我们看到struct student的大小为1个字节。
如果从先申请的放在高字节,则上边的输出为
           s2  s1
0000 |001 |0
即输出应该是:
64
1
网上有人说TURBO C是采用这种方式,我没试过。

三、位结构的位对齐问题
位结构的其实不存在位对齐问题,即位不需要对齐。其他方面,位结构和一般结构体类似,遵循结构体的对齐原则,

#include "stdio.h"

void main()
{
 union
 {
 struct student
 {
  unsigned char s1:1;
   unsigned char s2:2;
  unsigned char s3:2;
 }x;
 unsigned char c;
 }v;
 v.c=0;
 v.x.s1=0;
 v.x.s3=2;
printf("%d/n",v.c);
printf("%d/n",sizeof(struct student)); 
}
输出结果是:
16
1

因为它只按整体对齐,所以为
s1s2s3
0  0001000
即二进制00010000等于十进制16,而不是
s1s2    s3
0  00 0 01 00

再举一个位结构体的例子

#include "stdio.h"
void main()
{
 union
 {
 struct student
 {
  unsigned char s1:1;
   unsigned char s2:2;
  unsigned short s3:2;
 }x;
 unsigned short c;
 unsigned int d;
 }v;
 v.d=0;
 v.x.s1=0;
 v.x.s3=2;
printf("%d/n",v.d);
printf("%d/n",sizeof(struct student)); 
}
输出为:
131072
4

131072=(10 00000000 00000000)b
因为遵循结构体对齐原则,s3跳过了2个字节。
s1s2                               s3
0  00 00000| 00000000| 01

 
位域与字节序解析

如下代码

#include "stdio.h"   
struct  kk  
{  
unsigned a:2;  
unsigned b:3;  
unsigned c:2;  
unsigned d:1;  
}  
kt;  
int  main()  
{  
        char  result =3;  
        memcpy(&kt,&result,1);  
        printf( " a = %d, b = %d, c = %d, d = %d,ok/n" ,kt.a,kt.b,kt.c,kt.d);  
         return  1;  
}  


在sun unix上用cc编译,结果为0,0,1,1

2000上用VC6.0编译,结果为3,0,0,0


又如:

struct kk  {  
   char a:2;  
   char b:3;  
   char c:2;  
   char d:1;  
} kt;  


int main()  
{  
        char result = 3

        memcpy(&kt, &result, 1); 

        printf("a = %d, b = %d, c = %d, d = %d/n",kt.a,kt.b,kt.c,kt.d); 

        return 1;  
}

file bit
bit: ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (SYSV), dynamically linked (uses shared libs),for GNU/Linux 2.6.0, not stripped
./bit
a = 0, b = 0, c = 1, d = 1

$ file bit
bit: PE32 executable for MS Windows (console) Intel 80386 32-bit
$ ./bit
a = -1, b = 0, c = 0, d = 0

=================================================

C语言的位域虽然很多人强烈建议不要使用,但现有系统里还广泛存在位域的使用,所以还是很有必要理清楚的。

对big-endian和little-endian的区别,很多人认为是对多字节数据类型而言。其实,问题的本质不在这里。

两种endian区别的本质是由于CPU的数据引脚和系统地址总线的连接方向的不同。也就是说,高地址,低地址的区别不仅体现在“字节序”上,还体现在“比特序”上,只不过因为系统屏蔽了“比特序”的一些细节,所以看起来问题仅仅是字节之间的顺序问题了。

所以,对字节序相关的问题,如果能从两个角度来看问题,就很直接明了了。

先从系统地址总线的角度来分析数据的存放,然后从CPU的数据引脚的角度来解析数据....(对网络字节,可从网络数据传送地址的角度分析数据的存放,思想类同)

废话少说,分析一下上面的例子程序。(所有图表都从系统地址总线的角度看各个bit位)

C语言的位域中,每个field是严格按照bit地址从低到高排列的:

因此,从地址总线的角度看,四个域的排列顺序是(用于解析)

==============

-低->->->高-

  a b c d

==============

 

接下来看如何解析这个字节。

在little-endian机器上,result字节中各个比特的存放排列如下所示

==============

-低->->->高-

 ,11000000,

==============

对应到四个位域,得

a=3

b=0

c=0

d=0

 

在big-endian机器上,result字节中各个比特的存放排列如下所示

==============

-低->->->高-

 ,00000011,

==============

对应到四个位域,得

a=0

b=0

c=1

d=1

 

总结:

1.  C语言的位域类型使用时,各个field的摆放是按从低到高的bit顺序排列的。

2.  把数据的存放和解析分开,就可以很容易解释字节序的问题。

 

是这样的。这里有个术语的混淆。当多个字节组成一个长字(long)时,不同体系结构的cpu会按不同的方法来解释高低字节的顺序,这是字节序。小端的cpu会将低地址的字节认为是长字值的低位,高地址的字节认为是长字值得高位。大端的正好相反。

而在一个字节之内,不同体系结构的cpu会按不同的方法来解释高低比特的顺序,这是比特序。 小端的cpu将低位的比特认为是值得低位,高位的比特认为是值得高位。大端的正好相反。假设固定总线上的比特地址,从0比特到7比特的内存数据是 0 0 0 0 0 0 0 1。那么,小端的cpu读到的值就是0x80,而大端的cpu读到的值就是0x01。

INTEL的cpu字节序和比特序都是小端的。POWERPC的cpu一般都是大端的. 

明白以上道理,你可以分析IP header了.

struct  iphdr  
  {  
#if __BYTE_ORDER == __LITTLE_ENDIAN   
    unsigned  int  ihl:4;  
    unsigned  int  version:4;  
#elif __BYTE_ORDER == __BIG_ENDIAN   
    unsigned  int  version:4;  
    unsigned  int  ihl:4;  
#else   
# error "Please fix <bits/endian.h>"   
#endif   
    u_int8_t tos;  
    u_int16_t tot_len;  
    u_int16_t id;  
    u_int16_t frag_off;  
    u_int8_t ttl;  
    u_int8_t protocol;  
    u_int16_t check;  
    u_int32_t saddr;  
    u_int32_t daddr;  
     /*The options start here. */   
  };  



以及 tcp header
struct  tcphdr  
  {  
    u_int16_t source;  
    u_int16_t dest;  
    u_int32_t seq;  
    u_int32_t ack_seq;  
#  if __BYTE_ORDER == __LITTLE_ENDIAN   
    u_int16_t res1:4;  
    u_int16_t doff:4;  
    u_int16_t fin:1;  
    u_int16_t syn:1;  
    u_int16_t rst:1;  
    u_int16_t psh:1;  
    u_int16_t ack:1;  
    u_int16_t urg:1;  
    u_int16_t res2:2;  
#  elif __BYTE_ORDER == __BIG_ENDIAN   
    u_int16_t doff:4;  
    u_int16_t res1:4;  
    u_int16_t res2:2;  
    u_int16_t urg:1;  
    u_int16_t ack:1;  
    u_int16_t psh:1;  
    u_int16_t rst:1;  
    u_int16_t syn:1;  
    u_int16_t fin:1;  
#  else   
#   error "Adjust your <bits/endian.h> defines"   
#  endif   
    u_int16_t window;  
    u_int16_t check;  
    u_int16_t urg_ptr;  
};
原创粉丝点击