位域

把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。位域的定义和位域变量的说明位域定义与结构定义相仿。
声明格式如下:

struct
{
  type [member_name] : width ;
};

说明:

  • type 只能是 int、unsigned int、 signed int 三种类型。 但是编译器会扩展支持其他类型。
  • member_name 位域的名称
  • width 位域中位的数量,宽度必须小于或者等于指定类型的位宽度

struct {
    unsigned int widthValidated : 1;
    unsigned int heightValidated : 1;
}

这个结构将占用4字节的内存空间,但只有2位被用来存储值。

注意:

    1. 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
    1. 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍
    1. 整个结构体的总大小为最宽基本类型成员大小的整数倍
    1. 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,gcc采取压缩方式
    1. 如果位域字段之间穿插着非位域字段,则不进行压缩;(不针对所有的编译器)

4 和 5 跟编译器有较大的关系,使用时要慎重,尽量避免。

几个疑惑:

  1. 存储是怎么存的
  2. 超出了指定范围怎么存储
    带着疑惑做实验

存储 【物理存储是怎么样的】

相同类型未超出类型宽度

#include <stdio.h>
// 相同类型且宽度小于类型的宽度
typedef struct st
{
    unsigned char a:1;
    unsigned char b:2;
    unsigned char c:3;
};

int main() {
    struct st bit;
    bit.a = 1;
    bit.b = 3;
    bit.c = 7;
    printf("%d,%d,%d\n", bit.a, bit.b, bit.c);
    // 输出整个的结构体
    int len = sizeof(struct st);
    char *p = (char *)&bit;
    int i = 0;
    printf("size = %d\n", len);
    for(; i < len; i++) {
        printf("%2x\t", *(p+i));
    } 
    printf("\n");
    return 0;
}

运行结果如下:

[root@iz2zecj7a5r32f2axsctb9z byte]# ./byte1
1,3,7
size = 1
3f    

可以画出内存的布局:
内存布局

结论1: 类型相同,且宽度不满类型宽度,则从低位开始依次存放指定的内容。

相同类型超出类型宽度

#include <stdio.h>
// 相同类型且宽度大于类型的宽度
typedef struct st
{
    unsigned char a:2;
    unsigned char b:5;
    unsigned char c:5;
};

int main() {
    struct st bit;
    bit.a = 1;
    bit.b = 3;
    bit.c = 7;
    printf("%d,%d,%d\n", bit.a, bit.b, bit.c);
    // 输出整个的结构体
    int len = sizeof(struct st);
    char *p = (char *)&bit;
    int i = 0;
    printf("size = %d\n", len);
    for(; i < len; i++) {
        printf("%02x\t", *(p+i));
    } 
    printf("\n");
    return 0;
}

运行结果如下:

[root@iz2zecj7a5r32f2axsctb9z byte]# ./byte2
1,3,7
size = 2
0d    07

可以画出内存分布:
内存分布

结论2: 类型相同,且宽度超过类型宽度,则存放不下的字段从新的存储单元开始存储。

不同类型

#include <stdio.h>
// 相临不同类型
typedef struct st
{
    unsigned char a:2;
    unsigned char :0;
    unsigned char b:5;
    unsigned int c:5;
};

int main() {
    struct st bit;
    bit.a = 1;
    bit.b = 3;
    bit.c = 7;
    printf("%d,%d,%d\n", bit.a, bit.b, bit.c);
    // 输出整个的结构体
    int len = sizeof(bit);
    char *p = (char *)&bit;
    int i = 0;
    printf("size = %d\n", len);
    for(; i < len; i++) {
        printf("%02x\t", *(p+i));
    } 
    printf("\n");
    return 0;
}

运行结果:

[root@iz2zecj7a5r32f2axsctb9z byte]# ./byte3
1,3,7
size = 4
61    ffffffe3    40    00    

由结果可知gcc采用了压缩方式。

结论3: 相邻类型不同,不同编译器会采用不同的处理方法。

插入非位域字段

#include <stdio.h>
// 穿插非位域类型
typedef struct st
{
    unsigned char a:2;
    unsigned char :0;
    unsigned int c:5;
    long d;
};

int main() {
    struct st bit;
    // 输出整个的结构体
    int len = sizeof(bit);
    printf("size = %d\n", len);
    return 0;
}

运行结果:

[root@iz2zecj7a5r32f2axsctb9z byte]# ./byte4
size = 16

结论4: 如果位域字段之间穿插着非位域字段,则一般不进行压缩。

特别说明

相邻域不同

1 struct BitField1{
2     char element1   : 1;
3     short element2  : 5;
4     char element3   : 7;
5 };

在VC6中其sizeof为6,在gcc中为2。

位域注意

  • 位域的地址不能访问,因此不允许将&运算符用于位域。不能使用指向位域的指针也不能使用位域的数组(数组是种特殊指针)。

  • 位域不能作为函数返回的结果

  • 位域以定义的类型为单位,且位域的长度不能够超过所定义类型的长度。例如定义int a:33是不允许的

  • 位域可以不指定位域名,但不能访问无名的位域

    • 位域可以无位域名,只用作填充或调整位置,占位大小取决于该类型。例如,char :0表示整个位域向后推一个字节,即该无名位域后的下一个位域从下一个字节开始存放,同理short :0和int :0分别表示整个位域向后推两个和四个字节
  • 带位域的结构在内存中各个位域的存储方式取决于编译器,既可从左到右也可从右到左存储

字节序的影响

位域结构也要遵循比特序(类似字节序)

1 struct bitfield{
2     unsigned char a: 2;
3     unsigned char b: 6;
4 }

该位域结构占1个字节,假设赋值a = 0x01和b=0x02,则大字节机器上该字节为(01)(000010),小字节机器上该字节为(000010)(01)。因此在编写可移植代码时,需要加条件编译。

#include <stdio.h>

typedef union{
          unsigned char hex;
         struct{
              unsigned char low  : 4;
              unsigned char high : 4;
          };
}convert;

int main() {

    convert t ;
    t.low = 0x01;
    t.high = 0x02;

    printf("hex = 0x%0x\n", t.hex);
    return 0;
}

运行结果为:

[root@iz2zecj7a5r32f2axsctb9z byte]# ./byte5
hex = 0x21

在小字节机器上若low=0x01,high=0x02,则hex=0x21

例如: ip头部定义就使用了位域

struct ip
  {
#if __BYTE_ORDER == __LITTLE_ENDIAN
    unsigned int ip_hl:4;               /* header length */
    unsigned int ip_v:4;                /* version */
#endif
#if __BYTE_ORDER == __BIG_ENDIAN
    unsigned int ip_v:4;                /* version */
    unsigned int ip_hl:4;               /* header length */
#endif
....
}
文档更新时间: 2021-01-31 16:44   作者:周国强