The standard approach here is to use anonymous structs/unions, like this:
union mystruct
{
struct
{
uint16_t Reserved1 :3;
uint16_t WordErr :1;
uint16_t SyncErr :1;
uint16_t WordCntErr :1;
uint16_t Reserved2 :10;
};
uint16_t word_field;
};
or, if union is not good as a top level object,
struct mystruct
{
union
{
struct
{
uint16_t Reserved1 :3;
uint16_t WordErr :1;
uint16_t SyncErr :1;
uint16_t WordCntErr :1;
uint16_t Reserved2 :10;
};
uint16_t word_field;
};
};
This definition allows direct access to the inner fields, like:
mystruct s1;
s1.WordCntErr = 1;
Strictly speaking, compiler is not giving any guarantees on how different members of the union will overlap each other. It can use different alignments and even shifts. A lot of people here will readily point this out. Nevertheless, looking at this from the practical standpoint, if all fields of the union have the same size you can safely assume that they occupy the same piece of memory. For example, the code
s1.word_field = 0;
will zero out all bit fields. Tons of code are using this. It is unthinkable this will ever stop working.