走进结构体存储--位域

来源:互联网 发布:java培训班 编辑:程序博客网 时间:2024/04/28 12:43

http://www.cnblogs.com/pure/archive/2013/04/22/3034818.html

http://blog.csdn.net/sjin_1314/article/details/8683008

1、位域简介

       在嵌入式编程中,经常会遇到下面的结构:
[csharp] view plaincopyprint?
  1. struct  _data  
  2. {  
  3.   char   a:6;  
  4.   char   b:2;  
  5.   char   c:7;  
  6. }data;  

      在存储信息的时候,我们可能并不需要占用一个完整的字节,而只需占一个或几个二进制位,如要存储一个八进制数据,只需要3 个二进制位就够了。为了节省存储空间,C 语言提供了位域这种数据结构。所谓位域,就是把存储空间中的二进制位划分为几个不同的区域,并说明每个区域的位数,每个域有一个域名,允许在程序中按域名进行操作。

2、位域的定义及变量的说明

      定义位域的一般形式如下:

[csharp] view plaincopyprint?
  1. struct   位域结构名{  
  2.   类型说明符  位域名a:位域长度;  
  3.   ……  
  4.   类型说明符  位域名b:位域长度;  
  5. };  

     位域变量的说明:

     以开头提到结构体来说明:data 为struct _data 类型变量。占有2个字节,其中a:占6位,b占2位,c占7位;

3、位域定义的几点说明

      1)  一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也                可以有意使某位域从下一单元开始。

     2)  位域的长度不能大于指定类型固有长度,比如说int的位域长度不能超过32,bool的位域长度不能超过8。

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

          
[csharp] view plaincopyprint?
  1. struct data_ {  
  2.        int a:1  
  3.        int :2 /*该2位不能使用*/  
  4.        int b:3  
  5.        int c:2  
  6. }data;   

4、位域使用及存储空间说明

      1)位域的使用:

        
[csharp] view plaincopyprint?
  1. struct   位域结构名{  
  2.   类型说明符  位域名:位域长度;  
  3.   ……  
  4.   类型说明符  位域名:位域长度;  
  5. } 位域变量名1,位域变量名2……;  
[csharp] view plaincopyprint?
  1. /*其中,位域结构名可以省略掉,直接定义位域变量,如:*/  
  2. struct   {  
  3.   类型说明符  位域名:位域长度;  
  4.   ……  
  5.   类型说明符  位域名:位域长度;  
  6. } 位域变量名1,位域变量名2……;  
[csharp] view plaincopyprint?
  1. /*还可以先定义位域类型,再定义位域变量名,如:*/  
  2. struct   位域结构名{  
  3.   类型说明符  位域名:位域长度;  
  4.   ……  
  5.   类型说明符  位域名:位域长度;  
  6. };  
  7. struct   位域结构名  位域变量名1,位域变量名2……;  

      2) 位域存储空间说明

    示例1:
[csharp] view plaincopyprint?
  1. #include <stdio.h>  
  2.     struct data_ {  
  3.         char a  : 5;  
  4.         char b  : 3;  
  5.         char c  : 7;  
  6.     }data;   
  7.   
  8. int main()  
  9. {  
  10.   
  11.     printf("data 的起始地址是:%p\n",&data);  
  12.     printf("data 的占有的字节数:%d\n",sizeof(struct data_));  
  13. }  
  14. james@jsh:~$ ./a.out   
  15. data 的起始地址是:0x804a01c  
  16. data 的占有的字节数:2  

示例2:
[csharp] view plaincopyprint?
  1. #include <stdio.h>  
  2.     struct data_ {  
  3.         char a  : 5;  
  4.         char b  : 6;  
  5.         char c  : 7;  
  6.     }data;   
  7.   
  8. int main()  
  9. {  
  10.   
  11.     printf("data 的起始地址是:%p\n",&data);  
  12.     printf("data 的占有的字节数:%d\n",sizeof(struct data_));  
  13. }  
  14. james@jsh:~$ ./a.out   
  15. data 的起始地址是:0x804a01c  
  16. data 的占有的字节数:3  

    通过上面的2个例子,我们可以充分说了,上节中的第一条:

一个位域必须存储在同一个字节中,不能跨两个字节。

示例 3:
[csharp] view plaincopyprint?
  1. #include <stdio.h>  
  2.     struct data_ {  
  3.         char a  : 6;  
  4.         int b  : 22;  
  5.         char c  : 7;  
  6.     }data;   
  7.   
  8. int main()  
  9. {  
  10.   
  11.     printf("data 的起始地址是:%p\n",&data);  
  12.     printf("data 的占有的字节数:%d\n",sizeof(struct data_));  
  13. }  
  14. james@jsh:~$ ./a.out <span style="color:#ff0000;">  
  15. </span>data 的起始地址是:0x804a01c  
  16. data 的占有的字节数:8<span style="color:#ff0000;">  
  17. </span>  
示例4:
[csharp] view plaincopyprint?
  1. #include <stdio.h>  
  2.     struct data_ {  
  3.         char a  : 6;  
  4.         int b  : 30;  
  5.         char c  : 7;  
  6.     }data;   
  7.   
  8. int main()  
  9. {  
  10.   
  11.     printf("data 的起始地址是:%p\n",&data);  
  12.     printf("data 的占有的字节数:%d\n",sizeof(struct data_));  
  13. }  
  14. james@jsh:~$ ./a.out   
  15. data 的起始地址是:0x804a01c  
  16. data 的占有的字节数:12  

示例5:
[csharp] view plaincopyprint?
  1. #include <stdio.h>  
  2.     struct data_ {  
  3.         char a  : 6;  
  4.         int b  : 12;  
  5.         char c  : 7;  
  6.     }data;   
  7.   
  8. int main()  
  9. {  
  10.   
  11.     printf("data 的起始地址是:%p\n",&data);  
  12.     printf("data 的占有的字节数:%d\n",sizeof(struct data_));  
  13. }  
  14. james@jsh:~$ ./a.out   
  15. data 的起始地址是:0x804a01c  
  16. data 的占有的字节数:4  

上述测试:必须对内存对齐方式有一定的了解,简单说明下吧!内存对齐应遵循下来3个规则: 

1、按字节对齐 2、按字对齐 3、按半字对齐 (一个字是4个字节,半字2个字节)

 位域的存储首先应该遵循内存的对齐放弃,再遵循上述3节第一条部分。

再补充点就是:结构体成员排列最好的方式,按类型占有字节的大小,按大到小排列!

 
[csharp] view plaincopyprint?
  1. #include <stdio.h>     
  2. #pragma pack (2)   
  3. struct  _data  
  4. {  
  5.   int      a:16;  
  6.   unsigned  char   b:5;  
  7.   char      c:5;  
  8. }data;  
  9. void main()    
  10. {   
  11.   int *p=(int *)&data;  
  12.   printf(" 位域结构的起始地址为:%d\n\n",p);  
  13.   data.a=2;  
  14.   printf(" 整型指针p 所指向的单元存储的值为:%d\n",*p);  
  15.   printf(" 位域a 的值为:%d\n",data.a);  
  16.    
  17.   char *p1=(char*)(p+1);  
  18.   data.b=18;  
  19.   printf("\n字符指针p1所指向的单元存储的值为:%d\n",*p1);  
  20.   printf(" 位域b 的值为:%d\n",data.b);  
  21.   data.c=255;  
  22.   char *p2;  
  23.   p2 = p1+1;  
  24.   printf("\n字符指针p2所指向的单元存储的值为:%d\n",*p2);  
  25.   printf(" 位域c 的值为:%d\n",data.c);  
  26.   return ;  
  27. }    
  28. 运行结果:  
  29. 位域结构的起始地址为:4233624  
  30. 整型指针p 所指向的单元存储的值为:2  
  31. 位域a 的值为:2  
  32. 字符指针p1所指向的单元存储的值为:18  
  33. 位域b 的值为:18  
  34. 字符指针p2所指向的单元存储的值为:31  
  35. 位域c 的值为:-1。  


 

& d a t a 为d a t a 位域结构的起始地址,将其强制转换为i n t 型指针,并赋值给p ,所以p 的
值就是d a t a 位域的起始地址,即4 2 3 3 6 2 4 ,p 指针指向的就是以4 2 3 3 6 2 4 为起始地址的连续
4 个字节的内存单元;接下来执行“c h a r   * p 1 = ( c h a r * ) ( p + 1 ) ; ”使p 1 的值为4 2 3 3 6 2 8 ,p 1 就指
向地址为4 2 3 3 6 2 8 的内存单元;执行“p 2   =   p 1 + 1 ; ”使p 2 的值为4 2 3 3 6 2 9 ,c h a r 型指针指向
地址为4 2 3 3 6 2 9 的内存单元。我们发现,* p 的值和位域a 的值相同。由此可以看出,V C + +  
6 . 0 在编译的时候,对于那些没有使用的位域段,编译器对其进行填充0 的处理。看看位域c
的运行结果,我们发现输出与输入不相符,这是因为在编译的过程中对c h a r 型位域默认执行
有符号处理,所以输出值为- 1 ,而对位域b 指定了无符号的处理方式,所以输出与输入完

全一致。


几篇较全面的位域相关的文章:

http://www.uplook.cn/blog/9/93362/

C/C++位域(Bit-fields)之我见

C中的位域与大小端问题

内存对齐全攻略–涉及位域的内存对齐原则

本文主要对位域相关知识进行了一下梳理,参考如下:

C语言中的位域

史上最全的C位域总结2

C结构体之位域(位段)

 

C/C++中以一定区域内的位(bit)为单位来表示的数据成为位域,位域必须指明具体的数目。

位域的作用主要是节省内存资源,使数据结构更紧凑。

1. 一个位域必须存储在同一个字节中,不能跨两个字节,故位域的长度不能大于一个字节的长度。

如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:

复制代码
   struct BitField   {      unsigned int a:4;  //占用4个二进制位;      unsigned int  :0;  //空位域,自动置0;      unsigned int b:4;  //占用4个二进制位,从下一个存储单元开始存放;      unsigned int c:4;  //占用4个二进制位;      unsigned int d:5;  //占用5个二进制位,剩余的4个bit不够存储4个bit的数据,从下一个存储单元开始存放;      unsigned int  :0;  //空位域,自动置0;      unsigned int e:4;  //占用4个二进制位,从这个存储单元开始存放;   };
复制代码

2. 取地址操作符&不能应用在位域字段上;

3. 位域字段不能是类的静态成员;

4. 位域字段在内存中的位置是按照从低位向高位的顺序放置的;

复制代码
struct BitField  {    unsigned char a:2;  //最低位;    unsigned char b:3;    unsigned char c:3;  //最高位;  };  union Union  {    struct BitField bf;    unsigned int n;  };  union Union ubf;  ubf.n = 0;    //初始化;  ubf.bf.a = 0; //二进制为: 000  ubf.bf.b = 0; //二进制为: 000  ubf.bf.c = 1; //二进制为: 001  printf("ubf.bf.n = %u\n", ubf.n);
复制代码

位域中的位域字段按照从低位向高位顺序方式的顺序来看,那么,a、b、c这三个位域字段在内存中的放置情况是:

最高位是c:001,中间位是b:000,最低位是a:000;所以,这个位域结构中的8二进制内容就是: 00100000,总共8个位,其十进制格式就是32;

实际上打印出来的ubf.n值就是32;

ubf.n = 100; //二进制为: 01100100

printf("ubf.bf.a = %d, ubf.bf.b = %d, ubf.bf.c = %d\n", ubf.bf.a, ubf.bf.b, ubf.bf.c);

此时,对于位域ubf.bf来说,其位于字段仍然按照从低位向高位顺序方式的顺序放置,则,最高位是c:011,中间位是b:001,最低位是a:00;

所以,ubf.bf.a = 0; ubf.bf.b = 1; ubf.bf.c = 3;

实际上打印出来的结果也的确如此;不够存储下一个位域的4位,故设为空位域,不使用,自动置0;e从第四个字节处开始存放,占用4位;

5. 位域的对齐

1. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;

2. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;

3.如果相邻的两个位域字段的类型不同,则各个编译器的具体实现有差异,VC6采取不压缩方式,GCC和Dev-C++都采用压缩方式;

4. 整个结构体的总大小为最宽基本类型成员大小的整数倍。

5. 如果位域字段之间穿插着非位域字段,则不进行压缩;(不针对所有的编译器)

复制代码
  struct BFA  {    unsigned char a:2;    unsigned char b:3;    unsigned char c:3;  };  struct BFB  {    unsigned char a:2;    unsigned char b:3;    unsigned char c:3;    unsigned int  d:4;  //多出来这个位域字段;  };
复制代码

sizeof(BFA)=1, sizeof(BFB)=8;

这也说明了第三点中"相邻两个位于字段类型不相同时,VC6采取不压缩的方式"

6. 当要把某个成员说明成位域时,其类型只能是int,unsigned int与signed int三者之一(说明:int类型通常代表特定机器中整数的自然长度。short类型通常为16位,long类型通常为32位,int类型可以为16位或32位.各编译器可以根据硬件特性自主选择合适的类型长度.见The C Programming Language中文 P32)。

尽管使用位域可以节省内存空间,但却增加了处理时间,在为当访问各个位域成员时需要把位域从它所在的字中分解出来或反过来把一值压缩存到位域所在的字位中.

复制代码
#include <iostream> #include <memory.h> using namespace std; struct A {     int a:5;     int b:3; }; int main(void) {     char str[100] = "0134324324afsadfsdlfjlsdjfl";         struct A d;     memcpy(&d, str, sizeof(A));     cout << d.a << endl;     cout << d.b << endl;     return 0; }
复制代码

在32位x86机器上输出:

高位 00110100 00110011   00110001    00110000 低位       '4'       '3'       '1'          '0'  其中d.a和d.b占用d低位一个字节(00110000),d.a : 10000, d.b : 001

解析:在默认情况下,为了方便对结构体内元素的访问和管理,当结构体内的元素长度都小于处理器的位数的时候,便以结构体里面最长的元素为对其单位,即结构体的长度一定是最长的数据元素的整数倍;如果有结构体内存长度大于处理器位数的元素,那么就以处理器的位数为对齐单元。由于是32位处理器,而且结构体中a和b元素类型均为int(也是4个字节),所以结构体的A占用内存为4个字节。

上例程序中定义了位域结构A,两个个位域为a(占用5位),b(占用3位),所以a和b总共占用了结构A一个字节(低位的一个字节)。

当程序运行到14行时,d内存分配情况:

 高位 00110100 00110011   00110001    00110000 低位       '4'       '3'       '1'          '0'   其中d.a和d.b占用d低位一个字节(00110000),d.a : 10000, d.b : 001

 d.a内存中二进制表示为10000,由于d.a为有符号的整型变量,输出时要对符号位进行扩展,所以结果为-16(二进制为11111111111111111111111111110000)

 d.b内存中二进制表示为001,由于d.b为有符号的整型变量,输出时要对符号位进行扩展,所以结果为1(二进制为00000000000000000000000000000001)

 

另一个例子,来自http://blog.chinaunix.net/uid-28697486-id-3511598.htm

复制代码
#include "stdio.h"void main(int argn ,char *argv){    struct     test {        unsigned a:10;        unsigned b:10;        unsigned c:6;        unsigned :2;//this two bytes can't use        unsigned d:4;        }data,*pData;    data.a=0x177;    data.b=0x111;    data.c=0x7;    data.d=0x8;        pData=&data;    printf("data.a=%x data.b= %x data.c=%x data.d=%xn",pData->a,pData->b,pData->c,pData->d);//位域可以使用指针    printf("sizeof(data)=%dn",sizeof(data));   //4 bytes ,最常用的情况    struct testLen{    char a:5;    char b:5;    char c:5;    char d:5;    char e:5;    }len;        printf("sizeof(len)=%dn",sizeof(len));     //5bytes 规则2    struct testLen1{        char a:5;        char b:2;        char d:3;        char c:2;        char e:7;        }len1;    printf("sizeof(len1) =%dn",sizeof(len1));    //3bytes 规则1    struct testLen2{        char a:2;        char :3;        char b:7;        long d:20; //4bytes        char e:4;        }len2;    printf("sizeof(len2)=%dn",sizeof(len2));  //12 规则3,4,5,总长为4的整数倍,2+3 占1byte,b占1bye 由于与long对其,2+3+7 占4字节,后面 d 与 e进行了优化 占一个4字节    struct testLen3{        char a:2;        char :3;        char b:7;        long d:30;        char e:4;        }len3;    printf("sizeof(len3)=%dn",sizeof(len3));//12 规则3,4,5,总长为4的整数倍,2+3 占1byte,b占1bye 由于与long对其,2+3+7 占4字节,后面 d占一个4字节,为了保证与long对其e独占一个4字节}
复制代码

 

另:C++标准库提供了一个bitset 类模板,它可以辅助操纵位的集合。在可能的情况下应尽可能使用它来取代位域。


0 0
原创粉丝点击