5
struct m

{
   int parent:3;

   int child:3;

   int mother:2;
};

void main()
{

   struct m son={2,-6,5};

   printf("%d %d %d",son.parent,son.child,son.mother);
}

任何人都可以帮助说明为什么程序的输出是2 2 1

4

8 回答 8

27

取出显示的字段的所有有效位:

parent: 3 bits (1-sign bit + 2 more), value 010, result 2
child:  3 bits (1-sign bit + 2 more), value 010, result 2
mother: 2 bits (1 sign bit + 1 more), value  01, result 1

细节

需要指出的是,您的结构字段被声明为int位字段值。根据 C99-§6.7.2,2,以下类型都是等效的:intsignedsigned int. 因此,您的结构字段已签名。根据 C99-§6.2.6.2,2,您的一位应用于表示变量的“符号”(负或正)。此外,同一部分指出,除了符号位,其余位表示必须对应于关联的无符号剩余位数的类型。C99-§6.7.2,1 清楚地定义了这些位中的每一个如何表示 2 的幂。因此,通常用作符号位的唯一位是最高有效位(它是唯一剩下的位,但我很确定这是否是对标准的不准确解释,我会在适当的时候听到它)。您将负数指定为用于样本的测试值之一,这表明您可能知道这一点,但许多新接触位域的人却没有。因此,值得注意。

本答案的其余部分引用了 C99 标准的以下部分。第一个处理不同类型的促销,接下来是估值和潜在的价值变化(如果有的话)。最后一点对于理解如何int确定 bit-fields 类型很重要。

C99-§6.3.1.1:布尔值、字符和整数

2:如果 anint可以表示原始类型的所有值(受宽度限制,对于位域),则将该值转换为 an int;否则,将其转换为unsigned int. 这些被称为整数促销。整数提升不会改变所有其他类型。

C99-§6.3.1.3 有符号和无符号整数

  1. 当整数类型的值转换为_Bool以外的其他整数类型时,如果该值可以用新类型表示,则保持不变。
  2. 否则,如果新类型是无符号的,则通过在新类型中可以表示的最大值的基础上反复加减一,直到该值在新类型的范围内。
  3. 否则,新类型是有符号的,值不能在其中表示;结果是实现定义的,或者引发了实现定义的信号。

C99-§6.7.2.1 结构和联合说明符

10:位域被解释为具有由指定位数组成的有符号或无符号整数类型。如果值 0 或 1 存储到 _Bool 类型的非零宽度位域中,则该位域的值应与存储的值进行比较;_Bool 位域具有 _Bool 的语义。

考虑测试值的常规int位表示。以下是 32 位int实现:

value : s  bits 
    2 : 0  0000000 00000000 00000000 00000010   <== note bottom three bits
   -6 : 1  1111111 11111111 11111111 11111010   <== note bottom three bits
    5 : 0  0000000 00000000 00000000 00000101   <== note bottom two bits

遍历其中的每一个,应用上述标准参考中的要求。

int parent:3:第一个字段是 3 位有符号的int,并且被分配了十进制值2。右值类型 , 是否int包含左值类型 , int:3?是的,所以类型很好。该值 是否2适合左值类型的范围?好吧,2可以很容易地放入 中int:3,因此也不需要任何价值。第一个字段工作正常。

int child:3: 第二个字段也是一个 3 位有符号的int,这次被分配了十进制值-6。再一次,右值类型 ( int) 是否完全包含左值类型 ( int:3)?是的,所以类型也很好。但是,表示有符号值所需的最小位数为4位。( ),占最高位为符号位。因此,该值超出了 3 位带符号位域的允许存储范围。因此,结果是根据 §6.3.1.3-3实现定义的。-61010-6

int mother:2最后一个字段是 2 位有符号的int,这次被分配了十进制值 5。再一次,右值类型 ( int) 是否完全包含左值类型 ( int:2)?是的,所以类型也很好。然而,我们又一次面临着一个不适合目标类型的值。表示有符号正数所需的最小位数5为四:(0101)。我们只有两个可以合作。因此,结果再次按照 §6.3.1.3-3 由实现定义。

因此,如果我正确地理解了这一点,那么在这种情况下,实现会简单地删除除存储填充声明的位深度所需的所需位之外的所有位。那个hackery的结果就是你现在所拥有的。2 2 1

笔记

我完全有可能错误地颠倒了促销的顺序(我很容易迷失在标准中,因为我有阅读障碍并且会定期在脑海中翻转东西)。如果是这种情况,我会问任何对标准有更强烈解释的人,请向我指出这一点,我会相应地回答答案。

于 2013-01-15T11:49:39.693 回答
3

child和的位字段大小mother太小,无法包含您分配给它们的常量值,并且它们正在溢出。

于 2013-01-15T11:50:06.980 回答
3

您可以注意,在编译阶段您将收到以下警告:

test.c: In function ‘main’:
test.c:18:11: warning: overflow in implicit constant conversion
test.c:18:11: warning: overflow in implicit constant conversion

那是因为您已将变量定义为 int 的 3 位而不是整个 int。溢出意味着无法将您的值存储到 3 位内存中。

因此,如果您使用以下结构定义,您将避免编译中的警告,并且您将获得正确的值:

struct m

{
   int parent;

   int child;

   int mother;
};
于 2013-01-15T11:50:20.160 回答
2

child至少需要 4 位来保存-6( 1010)。并且mother需要至少 4 位来保存5( 0101)。

printf只考虑最后 3 位childso 它的打印2,它只考虑最后 2 位motherso 它的打印1

您可能认为child只需要 3 位来存储-6,但实际上它需要 4 位,包括符号位。负值用于以 2 的补码模式存储。

Binary equivalent of 6 is 110
one`s complement of 6 is 001
two`s complement of 6 is 010
sign bit should be added at MSB.

所以 的-6值为1010printf省略符号位。

于 2013-01-15T12:00:37.920 回答
2

您不能仅用 3 位表示 -6;同样,您不能仅用两位表示 5。

有没有理由为什么parent,childmother需要是位域?

于 2013-01-15T11:48:00.250 回答
1

您的位字段太小。另外:这只是一个练习,还是你试图(过早地)优化?你不会对结果很满意......它会很慢。

于 2013-01-15T11:50:03.283 回答
1

首先:

5101二进制的,所以它不适合 2 位。

-6 也不适合 3 位。

于 2013-01-15T11:48:11.767 回答
0

经过思考,我终于找到了原因:-)

2 用二进制表示为 0010

-6 作为 1010

和五个作为 0101

现在 2 可以仅使用 3 位来表示,因此它被存储为 010

-6 将以 3 位存储为 010

五个将存储在 2 位中作为 01

所以最终的输出是 2 2 1

谢谢大家的回复!!!!

于 2013-01-15T11:57:13.290 回答