位域那些事

    • 位域在内存中的布局是与机器有关的
    • 位域的类型必须是整型或枚举类型,带符号类型中的位域的行为将因具体实现而定
    • 取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域

    位域通常使用结构体声明, 该结构声明为每个位域成员设置名称,并决定其宽度:

    例如声明如下一个位域:

    1. {
    2. unsigned int code1: 2;
    3. unsigned int code3: 8;
    4. };
    5. struct _PRCODE prcode;

    该定义使 prcode包含 2 个 2 Bits 位域和 1 个 8 Bits 位域,我们可以使用结构体的成员运算符对其进行赋值

    1. prcode.code1 = 0;
    2. prcode.code2 = 3;
    3. procde.code3 = 102;

    赋值时要注意值的大小不能超过位域成员的容量,例如 prcode.code3 为 8 Bits 的位域成员,其容量为 2^8 = 256,即赋值范围应为 [0,255]。

    例如以下位域:

    1. struct box
    2. {
    3. unsigned int : 3;
    4. unsigned int b: 4;
    5. };

    该位域结构体中间有一个未命名的位域,占据 3 Bits,仅起填充作用,并无实际意义。 填充使得该结构总共使用了 8 Bits。但 C 语言使用 unsigned int 作为位域的基本单位,即使一个结构的唯一成员为 1 Bit 的位域,该结构大小也和一个 unsigned int 大小相同。 有些系统中,unsigned int 为 16 Bits,在 x86 系统中为 32 Bits。文章以下均默认 unsigned int 为 32 Bits。

    field1 + field2 = 34 Bits,超出 32 Bits, 编译器会将field2移位至下一个 unsigned int 单元存放, stuff.field1 和 stuff.field2 之间会留下一个 2 Bits 的空隙, stuff.field3 紧跟在 stuff.field2 之后,该结构现在大小为 2 * 32 = 64 Bits。

    这个空洞可以用之前提到的未命名的位域成员填充,我们也可以使用一个宽度为 0 的未命名位域成员令下一位域成员与下一个整数对齐。例如:

    1. struct stuff
    2. {
    3. unsigned int field1: 30;
    4. unsigned int : 2;
    5. unsigned int : 0;
    6. };

    这里 stuff.field1 与 stuff.field2 之间有一个 2 Bits 的空隙,stuff.field3 则存储在下一个 unsigned int 中,该结构现在大小为 3 * 32 = 96 Bits。

    学习代码见:learn.cpp

    位域的初始化与普通结构体初始化的方法相同,这里列举两种,如下:

    1. struct stuff s1= {20,8,6};

    或者直接为位域成员赋值

    1. struct stuff s1;
    2. s1.field1 = 20;
    3. s1.field2 = 8;
    4. s1.field3 = 4;

    利用重映射将位域归零

    1. int* p = (int *) &b1; // 将 "位域结构体的地址" 映射至 "整形(int*) 的地址"
    2. *p = 0; // 清除 s1,将各成员归零

    利用联合 (union) 将 32 Bits 位域 重映射至 unsigned int 型

    先简单介绍一下联合

    我们可以声明以下联合:

    1. union u_box {
    2. struct box st_box;
    3. unsigned int ui_box;
    4. };

    x86 系统中 unsigned int 和 box 都为 32 Bits, 通过该联合使 st_box 和 ui_box 共享一块内存。具体位域中哪一位与 unsigned int 哪一位相对应,取决于编译器和硬件。利用联合将位域归零,代码如下:

    1. u.ui_box = 0;

    学习参考自: