Reputation: 35229
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?
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.
Upvotes: 4
Views: 417
Reputation: 4848
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.
Upvotes: 4
Reputation: 181199
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?
Because the total number of bits is not greater than 8, you can rely on all of them being packed into the same addressable storage unit, but you cannot rely on
It is possible that your approach would work as you want with particular C implementations. If you find an implementation where it does, then you should be able to rely on it doing so consistently with that implementation. But it is not safe to assume that it will work with any particular implementation, and of course, that means it's not portable.
Bitfields almost never should be used.
Upvotes: 3
Reputation: 224862
If you stick to a single hardware implementation and use the same compiler and same set of compiler flags, you should get consistent behavior.
That being said, the layout of the fields will depend on the endianness of the machine in question. If you're working with big endian, the order is fine. If you're working with little endian, you'll need to reverse the fields. Also, you'll need to change the base type to unsigned char
so that the struct is 1 byte wide instead of 4.
For example, if you're using gcc, you can use the following:
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
typedef struct {
unsigned char hz_50 : 1
unsigned char fault : 1;
unsigned char fault_code : 2;
unsigned char wire_3 : 1;
unsigned char trigger : 1;
unsigned char mode : 1;
unsigned char vbias : 1;
} control_reg_t;
#elseif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
typedef struct {
unsigned char vbias : 1;
unsigned char mode : 1;
unsigned char trigger : 1;
unsigned char wire_3 : 1;
unsigned char fault_code : 2;
unsigned char fault : 1;
unsigned char hz_50 : 1
} control_reg_t;
#else
#error "unknown endianness"
#endif
Upvotes: 0