【问题标题】:can't understand bit fields in C无法理解 C 中的位域
【发布时间】:2020-09-11 17:14:34
【问题描述】:

大约 3-4 小时后开始阅读 C 中的位域,但我无法理解它们是如何工作的。比如我不明白为什么程序有输出:-1, 2, -3

#include <stdio.h>

struct REGISTER {
    int bit1 : 1;
    int      : 2;
    int bit3 : 4;
    int bit4 : 4;
};

int main(void) {
    struct REGISTER bit = { 1, 2, 13 };
    printf("%d, %d, %d\n", bit.bit1, bit.bit3, bit.bit4);
    return 0;
}

谁能给我一个解释?我倾向于认为,如果我在结构中使用unsigned,那么输出将是正数。但我不知道-3 来自哪里。

【问题讨论】:

  • 为什么第二个成员没有名字?
  • @ryyker 填充。而且确实没有任何意义。

标签: c bit bit-fields


【解决方案1】:

您的编译器将位域的 int 类型视为带符号的 int 类型。

考虑初始化器的二进制表示(考虑一个字节就足够了)

1  -> 0b00000001
2  -> 0b00000010
13 -> 0b00001101

因此,宽度等于 1 的第一个位域得到 1。对于具有一位的整数,该位是符号位。所以这样一个位域可以表示两个值 0 和 -1(在 2 的补码表示中)。

第二个初始化位域的宽度等于 4。因此它存储了等于 2 的位表示 0010。

第三个初始化位域的宽度也等于 4。所以它存储的位组合等于 1101。最高有效位是符号位,它被设置。所以位域包含一个负数。这个数字等于 -3。

   1101 ( = -3 )
+
   0011 ( = 3 )
   ====
   0000 ( = 0 )

【讨论】:

  • 您对观察到的行为的解释是正确但不完整:从技术上讲,初始化程序调用实现定义的行为,因为 1)签名是实现定义的,2)如果int 位字段被认为是由实现,113 超出了它们初始化的有符号类型的值范围。使用unsigned int绝对是OP更好的选择。
  • @chqrlie 我在一开始就写道“您的编译器将位字段的类型 int 视为带符号的 int 类型。”
  • 是的,您提到了关于签名问题的实现定义的行为。一个明确的短语可能会提高清晰度。实现定义的转换也是一个问题。通过将unsigned int 用于bit1bit4 的位字段类型,这两个问题都得到了充分解决。
  • @chqrlie 我希望问题的作者会阅读您的评论。:)
【解决方案2】:

int 位域是有符号还是无符号是实现定义的,因此在您关心 的任何程序中它实际上是一个错误价值 - 如果您关心价值,您将其限定为signedunsigned

现在,您的编译器将没有指定签名的位域视为有符号。 IE。 int bit4: 4 告诉该位域是 4 位宽 并且 有符号

13 不能在有符号的 4 位位域中表示,因为最大值为 7,无论负数的表示形式 - 2 的补码、1 的补码、符号和大小。现在将发生实现指定的转换:在您的情况中,位表示 1101 原样存储在 2 的补码有符号位域中, 被认为是 2 的补码负值-3

1 位有符号位域也是如此:一位是符号位,因此只有 2 个可能的值:二进制补码系统上的 0 和 -1。在一个补码系统或符号和大小上,一位位域根本没有任何意义,因为它只能存储 0 或陷阱值

如果您希望它能够存储值 13,则必须使用至少 5 位或使用 unsigned int: 4

【讨论】:

  • 这个答案中的解释使它脱颖而出。
  • 因此只有 3 个可能的值...您的意思是 3 个可能的情况还是 2 个可能的值?
  • @chqrlie 在计算机科学中有两件困难的事情:命名、缓存失效和一个错误。也许最初我正在考虑将陷阱/负零放在那里......
【解决方案3】:

请不要在此操作中使用带符号的 int。这会导致负结果。如果我们使用 unsigned int,则输出为负数

后面发生的事情是值 13 存储在等于 1101 的 4 位有符号整数中。MSB 是 1,所以它是一个负数,你需要计算二进制数的 2 的补码才能得到它内部完成的实际值。通过计算 2 的补码,你将得到值 0011,它相当于十进制数 3,因为它是负数,所以你得到 -3。

#include <stdio.h>

struct REGISTER {

  unsigned int bit1: 1;
  unsigned int :2;
  unsigned int bit3: 4;

  unsigned int bit4: 4;
};

int main(void) {
  struct REGISTER bit={1,2,13};
  printf("%d, %d, %d\n", bit.bit1, bit.bit3, bit.bit4);
  return 0;
}

这里的输出正好是 1,2,13

谢谢

【讨论】:

  • 如果我们使用unsigned int,输出是负数...你的意思是说:_如果我们使用unsigned int,输出就出来了是消极的_?
【解决方案4】:

这很简单,您将按如下方式初始化位域:

  1. 1 作为位长 1。这意味着 MSB 为 1,因为类型为 int,所以它是符号位。所以结果值为 -0-1 = -1。
  2. 2为位长4。MSB(符号位)为0,所以结果为正,即2。
  3. 13 表示位长 4。二进制为1101,因此 MSB 为 1(负),因此结果值为 -2-1 = -3。

您可以阅读更多关于二进制补码(英特尔架构上存储数字的格式)here. 简短版本是负数的 MSB=1,并且要计算它们的值,您可以取它们的负数 @987654324 @ 表示(没有符号位)减一。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多