Bit field

本文详细介绍了C语言中的位字段(bitfield)概念及其使用方法。包括位字段的基础语法、底层数据类型的选择、匿名域的应用、宽度为0的域的功能、以及位字段的一些限制条件等。此外还探讨了位字段在不同场景下的应用实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

c 语言中的bit field 是一种节省内存的方式, 用于struct 或者 union 的成员变量的声明。基本的语法是:

struct BF {
   ...
  type_specifier [declarator] : constant_expression;
   ...
};

采用这样一种方式的好处是,我们可以明确地指定某一个成员变量在内存中占用的空间(constant_expression bits). 比如:

struct BitArray {
    unsigned a : 1;
    unsigned b : 1;
};

// sizeof(BitArray) == 4

struct BitArray2{
    unsigned a;
    unsigned b;
};

// sizeof(BitArray2) == 8

上面的例子可以看出,使用了Bit field, 我们可以将相邻的变量ab 紧凑的排列到一个unsigned 中。 而如果不适用Bit field, 很明显编译器将会单独为ab 创建一个 unsigned 成员变量。

但是在使用bit field的时候,我们发现有很多限制或者注意点,这里罗列出大部分可能会碰到的问题。

  • 底层数据类型
    bit field 本身不是C/C++的基本类型,需要依附于底层数据在内存中的表示。一般多个bit fields 会共享一个底层数据类型所占用的内存空间。 底层类型只能是整形(char, short, int, long, long long 和 unsigned 变种) 或者枚举类型(enum ) :
enum E { E1, E2, E3, E4, E5, E6 = 200 };

struct SMixture {
    char a : 1, b : 1 ; //ok
    E e1 : 2,  e2 : 2;  //ok
    float f : 3 ;       //compiler error
};
  • 如果底层类型大小不足以容纳所有的 bit fields, 则在内存中开辟新的底层数据类型以容纳更多的域,但是多余的域不会横跨相邻的底层数据空间,而是开辟新的空间开始放置多余的Bit fields.
struct SMixture {
    char a : 1;  //start of first char
    char b : 3;  //continue in the first char
    char c : 5;  //start of second char
    char d : 4;  //start of third char
};

// sizeof(SMixture) == 3;
  • 匿名域(unnamed bit field)起padding的作用。 如果域的大小为0,则必须匿名(unnamed ).
struct SMixture {
   char c : 1;
   char d : 0; //compiler error
   char : 0;   //okay
};

width 为0的域是特殊域,表示该0-width bit field的下一个成员的地址必须以该0-width bit field 的类型的对齐方式对齐。

struct SMixture {
   char c : 1;   //start of char
   int : 0;
   short d : 1;   //start of short after alignment at type int boundary
};
//sizeof(SMixture) == 6;

struct SMixture2 {
   char c : 1;   //start of char
   int : 0;
   short d;   //start of short after alignment at type int boundary
};
//sizeof(SMixture2) == 6
  • 不能通过地址操作符(&)获取bit field的地址,因此bit field不能通过指针访问, 也不能通过引用访问。
int main(){
    SMixture s;
    short* ps = &s.d; //compiler error
    short& rs = s.d;  //compiler error
    return 0;
}
  • Bit field 不能是 static 成员。
struct SMixture {
   static char c : 1;   //compiler error
};
  • 使用bit field 的初衷是为了节省内存的消耗,但是最终的结果可能事与愿违。虽然bit field一定会减少类型的数据空间, 但是为了进行比特位操作,编译器不得不在生成的代码中加入额外的控制代码。因此最终的代码有可能比不使用bit field的情形还要大。另外一方面,直接访问整型数据类型一定会比访问bit fields要快。因此,在代码中使用bit field的情形可能相当有限。比如, 在这篇stackoverflow的文章中提到跨平台或者硬件的二进制文件的兼容问题,此时可能需要将某些成员变量强制对齐到某个位置:
/* from xnu/bsd/netinet/bootp.h */
/*
 * Bootstrap Protocol (BOOTP).  RFC 951.
 */
/*
 * HISTORY
 *
 * 14 May 1992 ? at NeXT
 *  Added correct padding to struct nextvend.  This is
 *  needed for the i386 due to alignment differences wrt
 *  the m68k.  Also adjusted the size of the array fields
 *  because the NeXT vendor area was overflowing the bootp
 *  packet.
 */
/* . . . */
struct nextvend {
  u_char nv_magic[4]; /* Magic number for vendor specificity */
  u_char nv_version;  /* NeXT protocol version */
  /*
   * Round the beginning
   * of the union to a 16
   * bit boundary due to
   * struct/union alignment
   * on the m68k.
   */
  unsigned short  :0;
  union {
    u_char NV0[58];
    struct {
      u_char NV1_opcode;  /* opcode - Version 1 */
      u_char NV1_xid; /* transcation id */
      u_char NV1_text[NVMAXTEXT]; /* text */
      u_char NV1_null;  /* null terminator */
    } NV1;
  } nv_U;
};
### Redis 中 Bitmap 的 BitField 命令详解 #### 什么是 BitField? `BITFIELD` 是 Redis 提供的一个用于操作位图(Bitmap)的高级命令。它允许用户在一个键中存储多个整数值,并通过按位操作来读取或修改这些值。相比传统的 `SETBIT` 和 `GETBIT`,`BITFIELD` 更加灵活且功能强大。 该命令支持多种数据类型的设置和获取,包括无符号数 (`unsigned`) 和有符号数 (`signed`),并可以执行原子性的增量操作[^1]。 --- #### BitField 命令语法 以下是 `BITFIELD` 命令的基本语法: ```plaintext BITFIELD key GET type offset SET type offset value INCRBY type offset increment OVERFLOW WRAP|SAT|FAIL ``` - **key**: 存储位图的键名。 - **GET type offset**: 获取指定偏移量上的值。 - **SET type offset value**: 设置指定偏移量上的值。 - **INCRBY type offset increment**: 对指定偏移量上的值增加一个增量。 - **OVERFLOW WRAP|SAT|FAIL**: 定义溢出行为: - `WRAP`: 溢出会环绕回最小值。 - `SAT`: 溢出会被截断为最大/最小值。 - `FAIL`: 如果发生溢出,则返回错误。 其中,`type` 表示要处理的数据类型,常见的形式如下: - `uN`: 无符号 N 位整数 (e.g., u8, u16, u32)。 - `iN`: 有符号 N 位整数 (e.g., i8, i16, i32)。 --- #### 示例代码 ##### 示例 1: 设置和获取单个位字段 以下是一个简单的例子,展示如何使用 `BITFIELD` 来设置和获取一个无符号 8 位整数。 ```bash # 初始化 BITFIELD 并设置第一个字节为 75 BITFIELD mybitfield SET u8 0 75 # 输出: array[1] # 获取第一个字节的值 BITFIELD mybitfield GET u8 0 # 输出: array[75] ``` 上述代码设置了键名为 `mybitfield` 的位图的第一个字节为 75,并成功检索到了这个值。 --- ##### 示例 2: 执行自增操作 下面的例子展示了如何对某个位置的值进行自增操作。 ```bash # 将第二个字节初始化为 100 BITFIELD counter SET u8 8 100 # 输出: array[100] # 对第二个字节的值增加 50 BITFIELD counter INCRBY u8 8 50 # 输出: array[150] # 再次获取第二个字节的值 BITFIELD counter GET u8 8 # 输出: array[150] ``` 此例说明了可以通过 `INCRBY` 子命令实现对特定位置的值进行安全的原子性更新。 --- ##### 示例 3: 处理溢出情况 当涉及到可能超出范围的操作时,可以选择不同的溢出策略。 ```bash # 设定第三个字节的最大值为 255 (即 u8 类型),尝试将其设为超过上限的值 BITFIELD overflow_test SET u8 16 255 INCRBY u8 16 1 OVERFLOW SAT # 输出: array[255] # 查看最终结果,由于选择了饱和模式(SAT),所以不会越界 BITFIELD overflow_test GET u8 16 # 输出: array[255] ``` 这里演示了如果启用饱和模式(`SAT`),即使发生了溢出也不会导致不合理的数值变化。 --- ### 总结 `BITFIELD` 是一种强大的工具,适用于需要高效管理大量小型状态的应用程序开发人员手中。无论是基本的状态跟踪还是复杂的计数器逻辑都可以借助这一特性轻松完成。然而需要注意的是合理规划好各个子域之间的边界以及考虑潜在的溢出风险以便选取合适的应对措施。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值