Bit field

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

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

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

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 成员变量。svg

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

  • 底层数据类型
    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;
};