C位字段变量正在打印意外值

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

取出显示的字段的所有重要位:

 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 -type非常重要。

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

2:如果int可以表示原始类型的所有值(由宽度限制,对于位字段),该值将转换为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 。 rvalue类型int是否包含左值类型int:3 ? 是的,所以类型很好。 2是否适合左值类型的范围? 好吧, 2可以很容易地适应int:3 ,因此也不需要值数据。 第一个字段工作正常。

int child:3 :第二个字段也是一个3位有符号的int ,这次被赋予十进制值-6 。 再一次,rvalue类型( int )是否完全包含左值类型( int:3 )? 是的,所以类型也很好。 但是, 最小位数要求表示-6 (有符号值)为4位。 ( 1010 ),将最显着位作为符号位。 因此,值-6超出了3位有符号位字段的允许存储范围。 因此,结果是根据§6.3.1.3-3 实现定义的

int mother:2最后一个字段是一个2位有符号的int ,这次被赋予十进制值5.再一次,rvalue类型( int )是否完全包含左值类型( int:2 )? 是的,所以类型也很好。 但是,我们再一次遇到了一个不适合目标类型的值。 表示有符号5的最小位数需求是4:(0101)。 我们只有两个人合作。 因此,结果再次按照§6.3.1.3-3 实现定义

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

注意

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

childmother的位字段大小太小,无法包含您分配给它们的常量值,它们会溢出。

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

 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; }; 

你不能只用3位表示-6; 同样,你不能只用两位代表5。

有没有理由为什么parentchildmother需要是位字段?

child需要最少4位才能保持-61010 )。 mother需要最少4位才能保持50101 )。

printf只考虑最后3位的child进程,所以它的打印2和它只考虑mother最后2位,所以它的打印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省略了符号位。

首先:

5是二进制的101 ,因此它不适合2位。

-6也不适合3位。

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

我终于在应用思想后找到了原因:-)

图2以二进制表示为0010

-6为1010

和五个为0101

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

-6将以3位存储为010

五个将以2位存储为01

所以最终的输出是2 2 1

谢谢大家的回复!!!!