C语言学习笔记---位字段

x33g5p2x  于2021-11-12 转载在 其他  
字(2.2k)|赞(0)|评价(0)|浏览(735)

在C语言中通常操作只有真假两种状态的的数据时使用布尔bool变量比较多,如果需要同时观察多个状态,这时候选择位操作效率会更高,用一个字节的8位分别表示8种状态。比较节省内存,处理起来效率更高。但是这种方法有一个缺点,就是看起来不直观,比如0x5C要想知道每一位的状态还得换算一下,同时要操作某一个单独位时,还必须使用位操作运算,比如位与、位或、异或。

如果熟悉单片机的就会想到,能不能在C语言中像操作寄存器那样直接操作一个字节的单独一位呢?

   比如这是一个单片机的端口方向配置寄存器,每一个端口有8个口,每个口可以单独配置为输出模式或者输入模式。比如现在设置 PA_DDR.DDR3 = 1;PA_DDR.DDR6 = 0; 那么就代表此时将PA口的端口3设置为输出模式,端口6设置为输入模式。用这种方式操作时,比直接使用位操作看起来更加的直观明了。

在C语言中也有这也得一种操作方式,它叫做 位字段 ,位字段是一个 signed int 或 unsigned int 类型变量中的一组相邻位。为字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度。

struct
{
    unsigned int Sun: 1;
    unsigned int Mon: 1;
    unsigned int Tue: 1;
    unsigned int Wed: 1;
    unsigned int Thur: 1;
    unsigned int Fri: 1;
    unsigned int Sat: 1;
} week;

在上面的这个 星期的结构中,提供了7个1位的字段,每一个字段就代表其中一天。这个就可以通过结构成员运算符点( . )来对每一个字符赋值。

week.Mon = 1;
        week.Tue = 1;
        week.Wed = 1;
        week.Thur = 1;
        week.Fri = 1;
        week.Sat = 0;
        week.Sun = 0;

通过结构运算符设置周一到周五的值为1,周末值为0。通过这样的方式可以单独操作字节中的某一位,和单片机中对于寄存器的操作就很类似了。由于每个字段只占了一位,所以字符的值只能是0或者1。还可以将每个字段设置为多个位。

struct {
    unsigned int code1 : 2;
    unsigned int code2 : 3;
    unsigned int code3 : 8;
} prcode;

创建了一个2位字段,一个3位字段,一个8位字段。可以这样赋值:

prcode.code1 = 3;
prcode.code2 = 7;
prcode.code3 =  255;

赋值的大小不能超过字段所能表示的最大范围。

在第一个例子中,字段的所有位数加起来只有7位,但是在第二个例子中,所有的字段位数加起来有13位,那么在内存中这两种情况是如何存储的呢?

在C语言中规定,如果声明的总位数超过了一个unsigned int 类型的大小,就会用到下一个unsigned int 类型的存储位置,一个字段不允许跨越两个unsigned int之间的边界,编译器会自动移动跨界的字段。保持unsigned int的边界对齐。

为了方便观察内存中的数据情况,将代码移植到单片机中。来观察在内存中的存储情况。

struct
{
    unsigned int Sun: 1;
    unsigned int Mon: 1;
    unsigned int Tue: 1;
    unsigned int Wed: 1;
    unsigned int Thur: 1;
    unsigned int Fri: 1;
    unsigned int Sat: 1;
} week;

struct
{
    unsigned int code1 : 5;
    unsigned int code2 : 5;
    unsigned int code3 : 7;
} prcode;

		week.Sun = 1;
		week.Mon = 0;
        week.Tue = 1;
        week.Wed = 0;
        week.Thur = 1;
        week.Fri = 0;
        week.Sat = 1;
       
      
        prcode.code1 = 0x03;
        prcode.code2 = 0x11;
        prcode.code3 =  0x22;

        cnt1 = sizeof(week);
        cnt2 = sizeof(prcode);

给两个位字段的每一段分别赋值,最后通过sizeof()计算着两个位字段占用内存的大小。

  week每个字段赋值完成后,内存中的数据变成了55。

  字段Sun为最低位,字段Sat为最高位。
  接下来存储prcode字段的数据。

  存储完 0x03和0x11时,内存中的数据变成了02 23.

  code1字段和code1字段各占了5位,所以最低5位是code1的值,紧接着5位是code2的值。其余位默认为0。

  最后给code3字段赋值,code3占7位,值为0x22。

  最后计算出week占用2个字节,prcode占用4个字节。

  当在内存中编译器自动边界对齐后,就会留下一个未命名的“洞”,可以使用未命名的字段宽度填充未命名的“洞”。如下代码:

struct
{
    unsigned int code1 : 1;
    unsigned int       : 4;
    unsigned int code2 : 1;
    unsigned int       : 1;
    unsigned int code3 : 1;
} prcode;

在code1和code2之间有4个空洞,在code2和code3之间有1个空洞。

  内存中的数据是 a1 。

  可以看到“空洞”的位置被自动填充为0。在C语言中规定,位字段是以unsigned int作为基本的布局单元,即使一个结构的成员是1位字段,在内存中该结构的大小也是一个unsigned int类型的大小。另外在位字段中布尔类型 (bool)也是可以使用的。

相关文章