C语言中,结构体位域(Bit Fields) 是用于精确控制结构体中各个字段的内存分配的一种方式。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几 个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

1、定义声明

位段成员必须声明为intunsigned intsigned int类型(shortcharlong)。位域的定义和位域变量的说明位域定义与结构定义类似,其形式为: 

struct 位域结构名
{ 位域列表 };

其中位域列表的形式为:

类型说明符 位域名:位域长度  

例如,

struct packed_struct {
   unsigned int f1:1;
   unsigned int f2:1;
   unsigned int f3:1;
   unsigned int f4:1;
   unsigned int type:4;
   unsigned int my_int:9;
}

也可以这样定义:

struct packed_struct {
   unsigned int f1:1;
   unsigned int f2:1;
   unsigned int f3:1;
   unsigned int f4:1;
   unsigned int type:4;
   unsigned int my_int:9;
} pack;

使用示例如下:

#include <stdio.h>
#include <string.h>
struct packed_struct {
    unsigned int f1:1;
    unsigned int f2:1;
    unsigned int f3:1;
    unsigned int f4:1;
    unsigned int type:4;
    unsigned int my_int:9;
} pack;
int main() {
    printf("%d\n", (int) sizeof(struct packed_struct));
    return 0;
}

2、位域的作用及存储规则

使用位域的主要作用是压缩存储,其大致规则为:

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

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

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

4) 如果位域字段之间穿插着非位域字段,则不进行压缩;

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

3、位域的使用场景

1)节省内存空间

在资源受限环境中,位域可以用来节省内存空间。例如,当需要用少量位表示设备的多种状态时,可以使用位域来定义这些状态。通过将多个状态变量压缩到一个整数中,可以显著减少内存占用。

2)优化位操作

位域可以用于优化位操作,特别是在需要直接操作硬件寄存器中的特定位或在网络传输中打包数据以减少字节占用的情况下。通过使用位域,可以直接对特定的位进行读写操作,而不需要进行复杂的位掩码和移位操作。

3)管理标志位

在网络协议中,位域常用于管理标志位。例如,TCP头部的URG、ACK、PSH、RST、SYN和FIN标志位可以通过位域来定义和管理。这样可以方便地对这些标志位进行设置和检查,而不需要单独定义多个布尔变量。

4、使用位域处理标志位

位域非常适合处理多种标志(flag),如一个字节(8位)可以存储多个标志。

#include <stdio.h>

// 使用位域定义多个标志位
struct Flags {
    unsigned int isVisible : 1;  // 1表示可见,0表示不可见
    unsigned int isEnabled : 1;  // 1表示启用,0表示禁用
    unsigned int isAdmin : 1;    // 1表示管理员,0表示普通用户
};

int main() {
    struct Flags userFlags;

    // 设置标志位
    userFlags.isVisible = 1;
    userFlags.isEnabled = 0;
    userFlags.isAdmin = 1;
 
    // 输出:isVisible: 1
    printf("isVisible: %u\n", userFlags.isVisible); 
    // 输出:isEnabled: 0
    printf("isEnabled: %u\n", userFlags.isEnabled);  
    // 输出:isAdmin: 1
    printf("isAdmin: %u\n", userFlags.isAdmin); 

    return 0;
}

5、位域与内存布局

位域的内存布局和填充可能因编译器不同而有差异,但通常会按照字段声明的顺序依次存储。当一个字段无法在当前字节中完全容纳时,会跨到下一个字节。

#include <stdio.h>

// 使用位域定义结构体
struct Example {
    unsigned int a : 4;  // 占4位
    unsigned int b : 4;  // 占4位
    unsigned int c : 8;  // 占8位
};

int main() {
    struct Example ex;

    // 输出结构体的大小
    printf("Size of struct Example: %lu bytes\n", sizeof(ex)); 

    return 0;
}

推荐文档