【C语言资料更新】结构体的“卫浴”(位域)

文/Edward

接下来再回到我们结构体的话题中来,我们之前讲结构体的时候,都是用int,char之类的数据类型来定义结构体的成员变量的,这些成员变量都有一个共性,就是他们的长度都是一个字节,或者一个的偶数倍。然而我们在存储某些信息时,并不需要一个完整的,而是只需要让这个变量占据一个或者几个二进制位。

可能有些读者会想,那我直接使用一个字节的长度来存储这些几位的变量即可,虽然会浪费一些存储空间,但是这些位的浪费对于现在的一些计算机或者单片机来说都是无关紧要的。确实,当我们定义一个变量的时候,对于目前计算机强大的硬件来说,定义以一个几个位的变量和定义一个几个字节的变量确实没有任何影响,反而字节单位变量比位变量有更大的存储空间,可以有效地防止长度溢出。

但是,当面对下面这种应用时,字节单位变量不仅没有任何好处,反而会大大增加我们程序的操作难度。如,在一个单片机系统中有一个寄存器。假设这个寄存器的长度为一个字节,它的功能是用来控制一个单片机的定时器以及反应一个定时器的状态,这个寄存器的第7,6位表示定时器的状态位TIM_STAT[1:0],第5,4,3,2位表示定时器时钟源的分频系数TIM_DIV[3:0],第1位表示定时器的溢出标志TIM_OVERFLOW,第0位表示定时器的工作开关TIM_START/STOP。具体这个假设的寄存器如图1所示。

图1 某寄存器

面对上面的这种应用,我们一般的做法就是定义一个unsigned char类型的变量Tim_Ctrl,然后进行位操作,比如要将TIM_STAT赋值状态0b11,那我们就可以使用位操作语句,“Tim_Ctrl |= 0b11000000;”,如果要将TIM_STST赋值状态0b00,就使用位操作语句“Tim_Ctrl &= ~(b11000000);”,这种微操作的方式非常繁琐,而且直观性很差。

那是否有一种数据类型可以支持这种位数比较少的变量呢?比如直接可以定义一个两位的变量,然后赋值状态0b11即可。在C语言中,常规的变量明显是不支持这种操作的,但是在结构体中却支持。这种C语言结构体中支持位操作的方式被称为“位域”,或者“位段”。

位段(或称“位域”,Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的好处:

  • 可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。

  • 位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。

  • 而位域这种数据结构的缺点在于,其内存分配与内存对齐的实现方式依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的。

位域的定义是在结构体中定义的时候完成的,其定义方式如下:

struct

{

  数据类型 变量名 :位长度;

  数据类型 变量名 :位长度;

} status;

比如,对于上面这个定时器的寄存器,我们可以定义如下:

struct

{

     unsigned char TIM_START_STOP : 1;

      unsigned char TIM_OVERFLOW : 1;

      unsigned char TIM_DIV : 4;

      unsigned char TIM_STAT : 2;

} register;

注意,这样定义好之后,整个结构体的位域定义时都是从低地址开始的。因此,当一个位域被定义好之后,其内存的分布如图2所示。

图2 位域内存分配

一旦当位域定义好之后,比如图2中的TIM_DIV,它所占用的比特数为4bits,因此虽然我们定义它的时候是用unsigned char类型去定义的,但是它最多能表示的二进制数只有4位,即范围为:0x00~0x0F。一旦当我们赋值超过了次范围,这个变量就会将多余的高位数据舍弃。

我们可以写一个程序来论证,按照图1所示的寄存器位分布,定义结构体位域变量,接着给它赋一个超出它长度的值,然后打印出来看看输出。如图3所示。

图3 结构体位域成员超出范围

从图3中我们可以看出,一旦当某个位域成员超出其位数大小之后,编译器先会抛出一个警告,然后将这个变量打印出来的值也是不对。那么为什么我们赋值20,却输出一个4呢?这是因为20的二进制数是0b00010100,而由于TIM_DIV变量只占有4个bit的存储空间,因此超出的部分会被舍弃,最终只保留低4位0b0100,因此这个变量打印出来的值为4。

由于这个结构体变量是占一个字节的存储空间,因此我们可以用一个指针打印出这个存储空间的全部内容。操作也很简单,我们只需定义一个unsignedchar类型的指针,并且使结构体的地址强行转换为一个“unsigned char *”类型,然后用指针指向它,最后引用指针将这个地址打印出来,就可以看到这个结构体全貌了。具体操作如图4所示。

图4 结构体数据

为什么最后结果是0xA7呢?因为整个结构体按照位域赋值之后如图5所示,最后转换成十六进制就是0xA7了。

图5 位域赋值之后

(0)

相关推荐

  • c语言结构体中的冒号的用法

    结构体中常见的冒号的用法是表示位域. 有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位.例如在存放一个开关量时,只有0和1两种状态,用一位二进位即可.为了节省存储空间,并使处理 ...

  • #define与typedef的区别?

    #define #define是预处理指令,在编译时不进行任何检查,只进行简单的替换 宏定义的一般形式为: #define 宏名 字符串 这里所说的字符串是一般意义上的字符序列,不要和C语言中的字符串 ...

  • 【C语言资料更新】第六十一集 结构体存储和用typedef定义结构体存储类型

    文 / Edward 结构体内部成员变量存储 前一小节,我们通过一个简单的例子来学习了结构体的定义和使用.事实上,结构体相当于是把一系列存在某种内在逻辑关系的变量成员包含在一个特定的群组中,从而实现便 ...

  • 【C语言资料更新】C语言中的枚举类型(enum)

    文/Edward 枚举是C语言里面所定义的一种基本数据类型,它可以使程序变得更加简介,更加易读.枚举的定义类似于我们数学里面的有限集合,如一周有7天,分别是SUNDAY.MONDAY.TUESDAY. ...

  • 【C语言资料更新】共用体联合体

    文/Edward 共用体又称为联合体,它是C语言中的一种特殊的数据类型.它允许用户在相同的内存位置存储不同的数据类型.用户可以定义一个带有多成员的共用体,但是任何时候这些成员都共享同一块内存.举个例子 ...

  • 【C语言笔记】结构体

    我们都知道C语言中变量的类型决定了变量存储占用的空间.当我们要使用一个变量保存年龄时可以将其声明为int类型,当我们要使用一个变量保存某一科目的考试成绩时可以将其声明为float. 那么,当我们要做一 ...

  • C语言为什么使用结构体效率会高?一文给你讲透

    https://m.toutiao.com/is/JphPbhp/ 作为过来人,我发现很多程序猿新手,在编写代码的时候,特别喜欢定义很多独立的全局变量,而不是把这些变量封装到一个结构体中,主要原因是图 ...

  • 【C语言更新】结构体中实现函数成员以及回调函数

    文/Edward 前面说,结构体内部的成员变量可以是普通变量,数组,除了这些变量之外,还可以是指针,结构体,枚举,共用体等.综上所述的结构体内部成员中,我们可以发现一个结构体内部的成员竟然不包含函数. ...

  • 【C语言更新】结构体的定义及使用

    文/Edward 首先先思考一个问题,假设某一天你去了一家策划公司,接到了一个策划需求,比如为新上市的某款手机写一个市场推广的文案,并且在电脑上面打印出来.那么在写这个文案的时候,你肯定是会需要着重地 ...

  • C语言知识总结——宏,枚举,结构体,共用体

    C语言知识总结——宏,枚举,结构体,共用体

  • 【C语言核心基础】基本运算、变量、数组、指针、函数、结构体...

    C 语言基础 // 引入头文件.里面包含了重要的 printf. #include <stdio.h> // 入口函数. // 参数一指输入的参数个数,参数二保存了所有参数. // 返回值 ...