比特域的类型是什么?

时间:2012-10-28 00:14:06

标签: c language-lawyer bit-fields c11

我在C标准中找不到指定的任何地方。例如,在

struct { signed int x:1; } foo;

foo.xint类型的左值,还是其他什么?它是int类型的左值似乎不自然,因为你不能在其中存储int类型的任何值,只有0或-1,但我找不到任何可以分配它的语言不同的类型。当然,在大多数表达式中使用它,无论如何它都会被提升为int,但实际类型会使C11与_Generic产生差异,我在标准中找不到任何关于bitfields的语言与_Generic互动。

7 个答案:

答案 0 :(得分:5)

正如Jonathan所引用的,p5清楚地说明了比特字段的类型。

您应该记住的是,6.3.1.1中的位域算术转换有一个特殊规则,基本上说明如果int可以表示所有值,例如位域在大多数表达式中将转换为int

_Generic中的类型应该是声明的类型(以符号小故障为模),因为似乎是算术转换不适用的共识。所以

_Generic((X), int: toto, unsigned: tutu)
_Generic(+(X), int: toto, unsigned: tutu)
如果X是无符号位字段,其宽度使所有值都适合int,则

会给您不同的结果。

答案 1 :(得分:3)

鉴于您包含signed限定符,那么可以存储在1位位字段中的唯一值确实为-1和0.如果省略了限定符,则它将是实现定义的“普通”int位字段是签名还是未签名。如果您指定了unsigned int,当然,值将为0和+1。

标准的相关部分是:

  

§6.7.2.1结构和联合说明符

     

¶4指定位域宽度的表达式应为整数常量   具有非负值的表达式,该值不超过对象的宽度   将指定的类型为冒号和表达式省略。 122)如果值为   零,声明不应有声明者。

     

¶5位字段的类型应为_Boolsigned intunsigned int或其他实现定义类型的限定或非限定版本。它是   实现 - 定义是否允许原子类型。

     

¶10位字段被解释为具有有符号或无符号整数类型   如果值0或1存储在非零宽度的位域   类型_Bool,比特字段的值应等于存储的值;一个_Bool   位字段具有_Bool的语义。

     

122)虽然_Bool对象中的位数至少为CHAR_BIT,但宽度(符号数和   _Bool的值位可能只有1位。

     

125)如上面6.7.2中所述,如果使用的实际类型说明符是int或typedef-name定义为int,   然后是实现定义位字段是有符号还是无符号。

脚注125指向:

  

§6.7.2类型说明符

     

¶5每个以逗号分隔的多重集都指定相同的类型,但对于位域,   它是实现定义的,说明符int是否指定了相同的类型   signed int或与unsigned int相同的类型。

答案 2 :(得分:3)

C11规范当然不会明确这一点,也许是有缺陷的。

我认为foo.x是一个非int类型的左值,但我的理由相当薄弱:

6.2.7第1段说:

  

如果两种类型的类型相同,则兼容类型

6.3第2段说:

  

将操作数值转换为兼容类型不会导致值或表示形式发生变化。

如果foo.xint类型的左值,那么它将与其他int兼容,因此foo.x = 5会导致foo.x具有值{{} {1}}(每6.3p2)。这显然不会发生,表明5foo.x不兼容,表明int不是foo.x类型的左值。

intfoo.x不兼容并不合理。也许没有转换(在6.3.1意义上),并且int通过标准中未讨论的某种机制获得其值。或者我可能误解了“算术操作数”的含义,而且6.3.1不适用于左值。

还有6.3.1.1第1段子弹2,其中说:

  
      
  • 有符号整数类型的等级应大于精度较低的任何有符号整数类型的等级。
  •   

foo.x的精度低于普通foo.x(当用作左值时,而不是当它“转换为存储在指定对象中的值”时,如6.3.2.1p2中所述),所以它必须具有不同的整数转换等级。这也表明它不是int

但我不确定我的解释是否有效或符合委员会的意图。

我建议提交一份有关此问题的缺陷报告。

答案 3 :(得分:2)

C11标准在6.7.2.1p5中说明:

  

位字段的类型应为_Bool,signed int,unsigned int或其他实现定义类型的限定或非限定版本。

这是一个约束,这意味着,如果一个程序声明一个类型不属于上述类别的位字段,则必须打印诊断。

然而它继续在6.7.2.1p10中说:

  

位字段被解释为具有由指定位数组成的有符号或无符号整数类型。

我相信他们的意思是,虽然你必须使用类似signed int x:n的东西声明位字段,但是左值表达式foo.x的类型是一些其他有符号整数类型,称之为T. T是一个由n位组成的有符号整数类型,必须符合Sec中给出的所有有符号整数类型的约束。 6.2.6。但是T不一定与名为signed int的标准有符号整数类型兼容。 (实际上,T可能与该标准类型兼容的唯一情况是,如果n恰好等于有符号整数中的位数。)

不幸的是,标准没有提供任何命名类型T的方法,所以我看不出如何在_Generic中使用它,至少不是以便携方式。 C语言的特定实现可以提供命名此类型的机制,但标准不强制它们。

答案 4 :(得分:1)

  

...实际类型使C11与_Generic

产生差异

1编译器如何处理位字段类型的结果如下所示。

8位和32位字段与通常的嫌疑人匹配。

1位位域的类型是什么?正如其他人所引用的那样,它的“名称”没有明确规定,但它不是任何预期的标准类型。

这并未引用规范,但确实展示了受尊重的编译器如何解释C规范。

GNU C11(GCC)版本5.3.0(i686-pc-cygwin)
由GNU C版本5.3.0,GMP版本6.1.0,MPFR版本3.1.4,MPC版本1.0.3编译

#define info_typename(X) _Generic((X), \
  _Bool: "_Bool", \
  unsigned char: "unsigned char", \
  signed char: "signed char", \
  char: "char", \
  int: "int", \
  unsigned : "unsigned", \
  default: "default" \
  )

#define TYPE_NAME(t) { printf("%s\n", info_typename(t)); }
#include <stdio.h>

int main() {
  struct {
    signed int x1 :1;
    signed int x8 :8;
    signed int x32 :32;
    _Bool b;
    signed char sc;
    char c;
    unsigned char uc;
    int i;
    unsigned u;
  } foo;
  TYPE_NAME(foo.b);
  TYPE_NAME(foo.sc);
  TYPE_NAME(foo.c);
  TYPE_NAME(foo.uc);
  TYPE_NAME(foo.i);
  TYPE_NAME(foo.u);
  TYPE_NAME(foo.x1);
  TYPE_NAME(foo.x8);
  TYPE_NAME(foo.x32);
}

输出

_Bool
signed char
char
unsigned char
int
unsigned
default           (int:1)
signed char       (int:8)
int               (int:32) 

请注意-Wconversion和以下代码,我收到此警告。这至少是一个编译器调用其小位字段类型:unsigned char:3

  

警告:从'unsigned int'转换为'unsigned char:3'可能会改变其值[-Wconversion]

  struct {
    unsigned int x3 :3;
  } foo;
  unsigned u = 1;
  foo.x3 = u; // warning

答案 5 :(得分:0)

我怀疑这取决于:

  1. 编译器
  2. 优化设置
  3. 位字段中的最大位数

答案 6 :(得分:0)

位域的类型是:

类型T

的位字段

其中T_Boolintsigned intunsigned int或某些实现定义类型。

在您的示例中,foo.x的类型为:signed int类型的位字段。

这与signed int不同,因为这两种类型不共享相同的约束和要求。

例如:

/* foo.x is of type bit-field of type signed int */
struct { signed int x:1; } foo; 

/* y is of type signed int */
signed int y;                     

/* valid, taking the size of an expression of type signed int */
sizeof y;

/* not valid, taking the size of an expression of type bit-field
 * of signed int */
sizeof foo.x;  

/* valid, taking the address of a lvalue of type signed int */
&y;            

/* invalid, taking the address of a lvalue of type bit-field
 * of signed int */
&foo.x;