5

I've had some experience in C before, however I have never seen the bitfields feature before. I know one can use bitmasks to isolate certain bits in a data structure, but why then bother with bitfields?

For example, say the bits we want to isolate are the first 3 least significant bits. Then we can write:

/* our bitmasks */
#define FIELD_A (1 << 0)
#define FIELD_B (1 << 1)
#define FIELD_C (1 << 2)

int main(void) 
{ 
    /* the data structure that contains our three fields */
    uint8_t flags;

    /* accessing field A (as a boolean) */
    int bool_a = !!(flags & FIELD_A);

    /* accessing field B (as a boolean) */
    int bool_b = !!(flags & FIELD_B);

    /* accessing field C (as a boolean) */
    int bool_c = !!(flags & FIELD_C);

    return 0;
}

Why would we choose to write this as:

static struct fields {
    int field_a : 1;
    int field_b : 1;
    int field_c : 1;
};

int main(void) 
{ 
    /* the data structure that contains our three fields */
    struct fields flags;

    /* accessing field A */
    int bit_a = flags.a;

    /* accessing field B */
    int bit_b = flags.b;

    /* accessing field C */
    int bit_c = flags.c;

    return 0;
}
nanoman
  • 341
  • 4
  • 11
  • 6
    Bitmasks approach is much more portable and robust. Stick with it. Even though bitfields look convenient. Unless you don't care about the layout of the bits. – Eugene Sh. Jul 09 '19 at 17:23
  • 1
    Your first example is incorrect. You want to shift by the number that you use to shift in the `#define`, not by `FIELD_X` itself. – Thomas Jager Jul 09 '19 at 17:23
  • `int bool_a = (flags & FIELD_A) >> FIELD_A_BIT_POS`; shorter `int bool_a = !!(flags & FIELD_A);`. If you do not need to have 0 or 1 value (in C the `true` is any nonzero value) you can drop `>>` or `!!` – 0___________ Jul 09 '19 at 17:29
  • Thank you for the corrections. I will edit my code to reflect them. – nanoman Jul 09 '19 at 17:30
  • @EugeneSh. it is a very pedantic approach. If the OP is going the use the same toolchain (for example in the uC development) the positions are very well-defined. – 0___________ Jul 09 '19 at 17:31
  • 3
    @P__J__ This is true for any "non-portable" claim. But in the case of bit-fields it is not even clear right from the code which bit is going to be LSB and which is MSB. Also it is not clear what width of the whole bitfield would be. And some other funny things, discoverable only by experimentation or deep digging in the compiler documentation. – Eugene Sh. Jul 09 '19 at 17:42
  • 3
    @P__J__ when coding, pedantry wins first place. – Weather Vane Jul 09 '19 at 18:01
  • @EugeneSh. It is rather difficult to program uCs without knowing the compiler. C standard knowledge is definitely no sufficient – 0___________ Jul 09 '19 at 18:14
  • 3
    `int field_a : 1;` is actually incorrect: an `int` bitfield with a single bit might be read as `-1` instead of `1` if set. You should write `unsigned int field_a : 1;` – chqrlie Jul 09 '19 at 18:30

2 Answers2

6

Bitfields are more handy to use than explicit bit masks, especially for lengths greater than 1. Hand coded bit twiddling is quite often broken in subtle ways.

The main problem with bitfields is the imprecise specification:

  • whether int typed bitfields are signed or unsigned is implementation defined.

  • the order and position of the bitfields in memory is implementation defined, which makes hardware mapping more risky. Even for a given endianness, the positions and order of bitfields are not specified precisely, which is a major shortcoming.

Note in particular that int field_a : 1; is actually problematic: an int bitfield with a single bit might be read as -1 instead of 1 if set. You should use unsigned int field_a : 1; etc.

chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • Maybe my interpretation of the standard is off here, but I think it's only implementation defined whether a bit-field is signed if you don't explicitly qualify its signedness. From C11 standard 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.* – Christian Gibbons Jul 09 '19 at 20:44
  • 1
    Combine with footnote 125 to get a clearer picture: *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.* – Christian Gibbons Jul 09 '19 at 20:51
1

I know one can use bitmasks to isolate certain bits in a data structure, but why then bother with bitfields?

One uses bitfields either to map a data structure whose field sizes do not all match your C implementation's built-in types, such as a TCP header, or simply to shrink the size of your data structure.

You can indeed pack and unpack data manually by using masks and shifting, but bitfields provide a more convenient syntax for that. In addition to just hiding the shifting and masking, bitfield access transparently handles sign extension issues and the special characteristics of _Bool, where appropriate.

The tradeoff is a loss of control over the details. If you pack and unpack manually, then you can have complete confidence and control of the layout, with great portability. If you use bitfields, on the other hand, and you care about the details of how the bits are arranged, then you need to rely on implementation details or on extensions to ensure that you have the layout you want, if that's even possible from your implementation at all.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157