cbit-fields

Using bit fields within a single byte


I'm familiar with the problems of using bit fields when communicating between processors -- see C/C++: Force Bit Field Order and Alignment and why endian-ness figures into the issue.

But my question is about using bit fields to specify the layout of bits within a one-byte register: Is it safe?

As a concrete example, I'm working with a device with a byte-wide control register documented as follows:

D7 (msb) D6 D5 D4 D3:D2 D1 D0 (lsb)
Vbias Mode Trigger 3-wire Fault Code Fault 50 Hz

It would seem natural to define the following struct:

typedef struct {
  unsigned int vbias : 1;
  unsigned int mode : 1;
  unsigned int trigger : 1;
  unsigned int wire_3 : 1;
  unsigned int fault_code : 2;
  unsigned int fault : 1;
  unsigned int hz_50 : 1
} control_reg_t;

So the question: As I'm only using this struct within a given processor -- it will not be transmitted over the wire, etc -- is there any reason NOT to use this approach?

update

This is not a good approach for defining bits within a register, even if the struct stays local to one machine. As has been pointed out below, the ordering of the bits is compiler dependent, so vbias may end up as the most significant bit or the least significant bit.

Moral: unless you can guarantee which compiler is used to compile your code, don't use structs and bit fields to define the layout of registers.


Solution

  • The C Standard states that:

    The order of allocation of bit-fields within a unit (high-order to low-order or low-order to high-order) is implementation-defined.

    This means that whether vbias inhabits the most significant bit or least significant bit of the unit (int) is implementation-defined. So unless you are sure you already know the behavior on your system and that you will only use your code on that particular system, bit-fields will not make the cut.

    As far as I can tell, that is not actually contingent on the byte-order of your system.

    Given that your control register specifies which items belong in which bits of significance but C bit-fields do not, I would suggest storing your flags as a uint8_t if available or otherwise a unsigned char, and access them as follows:

    #define get_vbias(x) (((x) >> 7) & 1)
    #define get_mode(x) (((x) >> 6) & 1)
    #define get_trigger(x) (((x) >> 5) & 1)
    #define get_wire_3(x) (((x) >> 4) & 1)
    #define get_fault_code(x) (((x) >> 2) & 3)
    #define get_fault(x) (((x) >> 1) & 1)
    #define get_hz_50(x) ((x) & 1)
    

    And in case setting them the same way you would set the bit-field of a struct is appropriate for your use case:

    #define set(x, n, b) (((x) & ~(1 << (n))) | (!!(b) << (n)))
    #define set_vbias(x, b) set((x), 7, (b))
    #define set_mode(x, b) set((x), 6, (b))
    #define set_trigger(x, b) set((x), 5, (b))
    #define set_wire_3(x) set((x), 4, (b))
    #define set_fault(x) set((x), 1, (b))
    #define set_hz_50(x) set((x), 0, (b))
    // Handle this one separately because the rest are just one bit
    #define set_fault_code(x, n) (((x) & ~(3 << 2)) | (((n) & 3) << 2))
    

    Extracting them manually using bitwise operators will guarantee which item inhabits the LSB, MSB, and each bit in between.