大雄的位域学习笔记

写在前面,
这是我读其他人的文章写下的笔记,第一版和原文几乎相同。侵联删,
后续我也会加入我在其他地方学到的相关的东西,也就是说,这是我自己的一个学习笔记。
当然了,既然是学习笔记,除了链接原文外肯定还有其他内容
原文链接:https://github.com/Light-City/CPlusPlusThings/tree/master/basic_content/bit

1、位域是什么?

1)定义
“ 位域 “ 或 “ 位段 “(Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。
2)作用

  • 一是可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。
  • 二是位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。
  • 而这种数据结构的缺点在于,位段实现依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的。

3)注意

  • 位域在内存中的布局是与机器有关的
  • 一个位域必须存储在同一个字节中,不能跨两个字节,故位域的长度不能大于一个字节的长度。
    如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
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个二进制位,从这个存储单元开始存放;
   };
  • 位域的类型必须是整型或枚举类型 ,带符号类型中的位域的行为将因具体实现而定
  • 取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域
  • 位域字段不能是类的静态成员

位域的使用

位域通常使用结构体声明, 该结构声明为每个位域成员设置名称,并决定其宽度:

struct bit_field_name
{
	type member_name : width;
};

|

ElementsDescription
bit_field_name位域结构名
type位域成员的类型,必须为 int、signed int 或者 unsigned int 类型
memer_name位域成员名
ElementsDescription

例如声明如下一个位域:

struct _PRCODE
{
	unsigned int code1: 2;
	unsigned int cdde2: 2;
	unsigned int code3: 8;
};
struct _PRCODE prcode;

该定义使 prcode包含 2 个 2 Bits 位域和 1 个 8 Bits 位域,我们可以使用结构体的成员运算符对其进行赋值

prcode.code1 = 0;
prcode.code2 = 3;
procde.code3 = 102;

赋值时要注意值的大小不能超过位域成员的容量,例如 prcode.code3 为 8 Bits 的位域成员,其容量为 2^8 = 256,即赋值范围应为 [0,255]。

位域的大小与对齐

1)位域的大小

struct box 
{
	unsigned int a: 1;
	unsigned int  : 3;
	unsigned int b: 4;
};

该位域结构体中间有一个未命名的位域,占据 3 Bits,仅起填充作用,并无实际意义。 填充使得该结构总共使用了 8 Bits。但 C 语言使用 unsigned int 作为位域的基本单位,即使一个结构的唯一成员为 1 Bit 的位域,该结构大小也和一个 unsigned int 大小相同。 有些系统中,unsigned int 为 16 Bits,在 x86 系统中为 32 Bits。文章以下均默认 unsigned int 为 32 Bits。
2)位域的存放

  • 位域字段在内存中的位置是按照从低位向高位的顺序放置的;
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位;

3)位域的对齐

  1. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
  2. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
  3. 如果相邻的两个位域字段的类型不同,则各个编译器的具体实现有差异,VC6采取不压缩方式,GCC和Dev-C++都采用压缩方式;
  4. 整个结构体的总大小为最宽基本类型成员大小的整数倍
  5. 如果位域字段之间穿插着非位域字段,则不进行压缩;(不针对所有的编译器)
struct stuff 
{
	unsigned int field1: 30;
	unsigned int field2: 4;
	unsigned int field3: 3;
};

field1 + field2 = 34 Bits,超出 32 Bits, 编译器会将field2移位至下一个 unsigned int 单元存放, stuff.field1 和 stuff.field2 之间会留下一个 2 Bits 的空隙, stuff.field3 紧跟在 stuff.field2 之后,该结构现在大小为 2 * 32 = 64 Bits。
这个空洞可以用之前提到的未命名的位域成员填充,我们也可以使用一个宽度为 0 的未命名位域成员令下一位域成员与下一个整数对齐。 例如:

struct stuff 
{
	unsigned int field1: 30;
	unsigned int       : 2;
	unsigned int field2: 4;
	unsigned int       : 0;
	unsigned int field3: 3; 
};

这里 stuff.field1 与 stuff.field2 之间有一个 2 Bits 的空隙,stuff.field3 则存储在下一个 unsigned int 中,该结构现在大小为 3 * 32 = 96 Bits。

#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字节
}

位域的初始化与重映射

1)初始化
位域的初始化与普通结构体初始化的方法相同,这里列举两种,如下:

struct stuff s1= {20,8,6};

或者直接为位域成员赋值

struct stuff s1;
s1.field1 = 20;
s1.field2 = 8;
s1.field3 = 4;

2)位域的重映射 (Re-mapping)

  • 声明一个 大小为 32 Bits 的位域
struct box {
	unsigned int ready:     2;
	unsigned int error:     2;
	unsigned int command:   4;
	unsigned int sector_no: 24;
}b1;
  • 利用重映射将位域归零
int* p = (int *) &b1;  // 将 "位域结构体的地址" 映射至 "整形(int*) 的地址" 
*p = 0;                // 清除 s1,将各成员归零
  • 利用联合 (union) 将 32 Bits 位域 重映射至 unsigned int 型
    先简单介绍一下联合

“联合” 是一种特殊的类,也是一种构造类型的数据结构。在一个 “联合” 内可以定义多种不同的数据类型, 一个被说明为该 “联合” 类型的变量中,允许装入该 “联合” 所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的

“联合” 与 “结构” 有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和(空结构除外,同时不考虑边界调整)。而在 “联合” 中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。

我们可以声明以下联合:

union u_box {
  struct box st_box;     
  unsigned int ui_box;
};

x86 系统中 unsigned int 和 box 都为 32 Bits, 通过该联合使 st_box 和 ui_box 共享一块内存。具体位域中哪一位与 unsigned int 哪一位相对应,取决于编译器和硬件。 利用联合将位域归零,代码如下:

union u_box u;
u.ui_box = 0;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值