What is the type of a bitfield?

2020-02-21 07:18发布

I can't find anywhere in the C standard where this is specified. For example, in

struct { signed int x:1; } foo;

is foo.x an lvalue of type int, or something else? It seems unnatural for it to be an lvalue of type int since you cannot store any value of type int in it, only 0 or -1, but I can't find any language that would assign it a different type. Of course, used in most expressions, it would get promoted to int anyway, but the actual type makes a difference in C11 with _Generic, and I can't find any language in the standard about how bitfields interact with _Generic either.

7条回答
We Are One
2楼-- · 2020-02-21 07:51

The type of a bit-field is:

bit-field of type T

where T is either _Bool, int, signed int, unsigned int or some implementation-defined type.

In your example, foo.x is of type: bit-field of type signed int.

This is different than signed int because the two types don't share the same constraints and requirements.

For example:

/* 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;        
查看更多
祖国的老花朵
3楼-- · 2020-02-21 07:52

The C11 specification certainly does not make this clear, and is perhaps deficient.

I believe that foo.x is an lvalue with type other than int, but my justification is pretty weak:

6.2.7 paragraph 1 says:

Two types have compatible type if their types are the same.

6.3 paragraph 2 says:

Conversion of an operand value to a compatible type causes no change to the value or the representation.

If foo.x is an lvalue of type int, then it would be compatible with other ints so foo.x = 5 should result in foo.x having value 5 (per 6.3p2). That obviously can't happen, suggesting that foo.x is not compatible with int, suggesting that foo.x is not an lvalue of type int.

It doesn't really make sense that foo.x isn't compatible with int. Maybe no conversion (in the 6.3.1 sense) occurs, and that foo.x obtains its value via some mechanism not discussed in the standard. Or maybe I'm misunderstanding what "arithmetic operands" means, and that 6.3.1 doesn't apply to lvalues.

There's also 6.3.1.1 paragraph 1 bullet 2, which says:

  • The rank of a signed integer type shall be greater than the rank of any signed integer type with less precision.

foo.x has less precision than an ordinary int (when used as an lvalue, not when it "is converted to the value stored in the designated object" as described in 6.3.2.1p2), so it must have a different integer conversion rank. This also suggests that it is not an int.

But I'm not sure that my interpretation is valid or matches the intention of the committee.

I would recommend submitting a defect report about this.

查看更多
冷血范
4楼-- · 2020-02-21 07:56

Given that you included the signed qualifier, then the only values that can be stored in the 1-bit bit field are indeed -1 and 0. If you'd omitted the qualifier, it would be implementation defined whether the 'plain' int bit field was signed or unsigned. If you'd specified unsigned int, of course, the values would be 0 and +1.

The relevant sections of the standard are:

§6.7.2.1 Structure and union specifiers

¶4 The expression that specifies the width of a bit-field shall be an integer constant expression with a nonnegative value that does not exceed the width of an object of the type that would be specified were the colon and expression omitted.122) If the value is zero, the declaration shall have no declarator.

¶5 A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type. It is implementation-defined whether atomic types are permitted.

¶10 A bit-field is interpreted as having a signed or unsigned integer type consisting of the specified number of bits.125) If the value 0 or 1 is stored into a nonzero-width bit-field of type _Bool, the value of the bit-field shall compare equal to the value stored; a _Bool bit-field has the semantics of a _Bool.

122) While the number of bits in a _Bool object is at least CHAR_BIT, the width (number of sign and value bits) of a _Bool may be just 1 bit.

125) As specified in 6.7.2 above, if the actual type specifier used is int or a typedef-name defined as int, then it is implementation-defined whether the bit-field is signed or unsigned.

The footnote 125 points to:

§6.7.2 Type Specifiers

¶5 Each of the comma-separated multisets designates the same type, except that for bitfields, it is implementation-defined whether the specifier int designates the same type as signed int or the same type as unsigned int.

查看更多
够拽才男人
5楼-- · 2020-02-21 07:59

The C11 Standard states in 6.7.2.1p5:

A bit-field shall have a type that is a qualified or unqualified version of _Bool, signed int, unsigned int, or some other implementation-defined type.

This is a constraint, meaning, if a program declares a bit field with a type that does not fall into one of the categories above, a diagnostic must be printed.

However it then goes on to say in 6.7.2.1p10:

A bit-field is interpreted as having a signed or unsigned integer type consisting of the specified number of bits.

I believe what they mean is that, while you must declare the bit field with something like signed int x:n, the type of the lvalue expression foo.x is some other signed integer type, call it T. T is a signed integer type consisting of n bits and must comply with the constraints on all signed integer types given in Sec. 6.2.6. But T is not necessarily compatible with the standard signed integer type named signed int. (In fact, the only situation in which it is possible for T to be compatible with that standard type is if n happens to equal the number of bits in a signed int.)

Unfortunately, the Standard does not provide any means to name the type T, so I can't see how it can be used in a _Generic, at least, not in a portable way. Specific implementations of the C language may provide a mechanism to name this type, but the Standard does not force them to.

查看更多
贪生不怕死
6楼-- · 2020-02-21 08:00

As Jonathan already cited, p5 clearly states what the type a bit-field has.

What you should have also in mind is that there is a special rule for bit-field arithmetic conversions in 6.3.1.1, basically stating that if an int can represent all values such a bit-field converts to an int in most expressions.

What the type would be in a _Generic should be the declared type (modulo the sign glitch), since it seems to be consensus that arithmetic conversions don't apply, there. So

_Generic((X), int: toto, unsigned: tutu)
_Generic(+(X), int: toto, unsigned: tutu)

could give you different results if X is an unsigned bit-field with a width that has all values fit into an int.

查看更多
放荡不羁爱自由
7楼-- · 2020-02-21 08:00

... the actual type makes a difference in C11 with _Generic

The results of how 1 compiler treats the bit field types is shown below.

The 8-bit and 32-bit fields match the usual suspects.

What is the type of a 1-bit bitfield? As others have cited, its "name" is not clearly specified, but it is not any of the expected standard types.

This does not cite a specification, but does demonstrate how a respected compiler interpreted the C spec.


GNU C11 (GCC) version 5.3.0 (i686-pc-cygwin)
compiled by GNU C version 5.3.0, GMP version 6.1.0, MPFR version 3.1.4, MPC version 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);
}

Output

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

Note with -Wconversion and the below code, I get this warning. So that is at least what one compiler calls its small bit field type: unsigned char:3.

warning: conversion to 'unsigned char:3' from 'unsigned int' may alter its value [-Wconversion]

  struct {
    unsigned int x3 :3;
  } foo;
  unsigned u = 1;
  foo.x3 = u; // warning
查看更多
登录 后发表回答