位域 VS. 位掩码

5

我之前有C语言的经验,但我从未接触过位域特性。我知道可以使用位掩码来隔离数据结构中的某些位,但是为什么还要使用位域呢?

例如,假设我们要隔离的位是最不显著的前3位。那么我们可以编写:

/* our bitmasks */
#define FIELD_A (1 << 0)
#define FIELD_B (1 << 1)
#define FIELD_C (1 << 2)

int main(void) 
{ 
    /* the data structure that contains our three fields */
    uint8_t flags;

    /* accessing field A (as a boolean) */
    int bool_a = !!(flags & FIELD_A);

    /* accessing field B (as a boolean) */
    int bool_b = !!(flags & FIELD_B);

    /* accessing field C (as a boolean) */
    int bool_c = !!(flags & FIELD_C);

    return 0;
}

为什么我们要把它写成这样:

static struct fields {
    int field_a : 1;
    int field_b : 1;
    int field_c : 1;
};

int main(void) 
{ 
    /* the data structure that contains our three fields */
    struct fields flags;

    /* accessing field A */
    int bit_a = flags.a;

    /* accessing field B */
    int bit_b = flags.b;

    /* accessing field C */
    int bit_c = flags.c;

    return 0;
}

6
位掩码方法更加可移植和健壮,建议坚持使用它。虽然位域看起来很方便,但除非您不关心位的布局。 - Eugene Sh.
1
你的第一个例子是不正确的。你想要移位的数字应该是你在 #define 中定义的数字,而不是 FIELD_X 本身。 - Thomas Jager
3
对于任何“非可移植”的声明都是如此。但是在位域的情况下,从代码中甚至无法明确哪个位将成为最低有效位和最高有效位。而且还不清楚整个位域的宽度是多少。还有其他一些有趣的事情,只有通过实验或深入挖掘编译器文档才能发现。 - Eugene Sh.
3
编程时,一丝不苟排在第一位。 - Weather Vane
3
int field_a : 1; 实际上是不正确的:一个只有一位的int位域如果被设置可能会被读作 -1 而非 1。你应该写成 unsigned int field_a : 1; - chqrlie
显示剩余4条评论
2个回答

6

相较于显式位掩码(bit masks),位域(bitfields)在长度大于1时更方便使用。手写的位操作往往容易出现微妙的错误。

位域的主要问题在于精确定义:

  • int 声明的位域是有符号还是无符号,取决于具体实现。

  • 位域在内存中的顺序和位置是由具体实现定义的,这使得硬件映射更加危险。即使对于给定的字节序(endianness),位域的位置和顺序也没有被严格地定义,这是一个重大缺陷。

特别注意的是 int field_a : 1; 实际上存在问题:如果设置了该位,单比特位域可能被读为 -1 而不是 1。应该使用 unsigned int field_a : 1; 等。


也许我对标准的解释有误,但我认为如果您没有明确指定位域的符号性,则它是否带符号只是实现定义。来自C11标准6.7.2.1p5:位域应具有限定或未限定版本的_Boolsigned intunsigned int或其他一些实现定义类型的类型。 - Christian Gibbons
1
结合脚注125,可以更清楚地了解:如上文6.7.2所指定的,如果实际使用的类型说明符是int或被定义为inttypedef名称,则位域是有符号还是无符号是由实现定义的。 - Christian Gibbons

1

我知道可以使用位掩码来隔离数据结构中的某些位,但为什么还要使用位域?

使用位域是为了映射一个数据结构,其中字段大小并不完全匹配您的C实现内置类型,例如TCP标头,或者只是为了缩小数据结构的大小。

您确实可以通过使用掩码和移位来手动压缩和解压数据,但位域提供了更方便的语法。除了隐藏移位和掩码之外,位域访问透明地处理符号扩展问题和_Bool的特殊特性(如果适用)。

这种权衡是对细节失去控制。如果您手动打包和解包,那么您可以完全信任和控制布局,并具有很好的可移植性。另一方面,如果您使用位域,并且您关心比特位如何排列的细节,则需要依赖于实现细节或扩展来确保您拥有所需的布局,如果从您的实现中可能实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接