我创建了一个字段大小为1位的字段,并使用int
代替unsigned
。后来,当我试图检查字段的值时,我发现值为-1。
我使用此代码检查二进制represantation和我的位字段的值:
#include <stdio.h>
#include <stdlib.h>
union {
struct {
int bit:1;
} field;
int rep;
} n;
int main() {
int c, k;
n.field.bit=1;
for (c = 31; c >= 0; c--)
{
k = n.rep >> c;
if (k & 1)
printf("1");
else
printf("0");
}
printf("\n %d \n", n.field.bit);
return 0;
}
输出是: 00000000000000000000000000000001
-1
在这种情况下,为什么我的位字段的值为-1,当我使用signed int而不是unsigned时,它总是一个负数?
答案 0 :(得分:7)
除了可以保存int
位之外,你不应该使用普通n
作为位域类型 - 根据C11标准,它实际上是实现 - 定义位字段中的int
是有符号还是无符号6.7.2p5:
5每个以逗号分隔的多重集都指定相同的类型,但对于位字段,实现定义是否指定符
int
指定的类型与{{ 1}}或与signed int
相同的类型。
在您的情况下,unsigned int
指定与int
相同的类型;这是the default in GCC:
是否将“plain”int位字段视为signed int位字段或无符号int位字段(C90 6.5.2,C90 6.5.2.1,C99和C11 6.7.2, C99和C11 6.7.2.1)。
默认情况下,它被视为
signed int
,但可以通过signed int
选项进行更改。
然后是实现定义有符号数是一个补码还是二进制补码 - 如果它们是一个补码,那么唯一可以存储在1位的值是符号位,因此为0;所以一位的有符号位字段在一个补码上没有意义。但是,您的系统使用2的补码 - 例如what GCC always uses:
是否使用符号和幅度,二进制补码或一个补码表示有符号整数类型,以及非常值是陷阱表示还是普通值(C99和C11 6.2.6.2)。
GCC仅支持两个补码整数类型,所有位模式都是普通值。
因此位值-funsigned-bitfields
和1
根据带符号的二进制补码进行解释:前者有符号位设置,所以它是负数(0
)而后者没有设置符号位,因此它是非负的(-1
)。
因此,对于2位的有符号位域,2的补码机上可能的位模式及其整数值
0
- 00
值为0 int
- 01
值为1 int
- 10
值-2 int
- 11
值-1 在n位字段中,最小有符号数为 - 2 ^(n - 1),最大值为2 ^(n-1) - 1.
现在,当对等级小于int
的有符号整数操作数执行算术运算时,首先将其转换为int
,因此值int
是符号扩展的到全宽-1
; 默认参数促销也是如此;传入int
时,该值会被符号扩展为(全宽)int
。
因此,如果您希望从一位位域获得合理的值,请使用printf
或者如果要将其理解为布尔标志,unsigned bit: 1;
答案 1 :(得分:1)
当您调用可变参数函数(如printf
)时,某些参数已提升。例如,位字段经历integer promotion,其中它被提升为普通的int
值。该促销带来了签名扩展(因为您的位字段的基本类型已签名)。此签名扩展名为-1
。
使用位字段时,始终使用无符号类型作为基础。