Why is this UNION like a struct?

Viewed 413

I came across some code (it's in a library from Microchip) which has a union.

All was good, until I saw it assigned values to different members of the union right after each other. My immediate thought was "They are over writing the same location..." Then I decided to do a test. By every measure I thought I understood, this union should be one byte (8 bits). But it's not... it's 4 bytes.

#pragma pack(1)
typedef union _STATUS
{
    BYTE Val;
    struct {
        unsigned BC8 : 1;
        unsigned BC9 : 1;
        unsigned BSTALL : 1;
        unsigned DTSEN : 1; 
        unsigned INCDIS : 1;
        unsigned KEN : 1;   
        unsigned DTS : 1;   
        unsigned UOWN : 1;  
    };
    struct {
        unsigned BC8 : 1;
        unsigned BC9 : 1;
        unsigned PID0 : 1;
        unsigned PID1 : 1;
        unsigned PID2 : 1;
        unsigned PID3 : 1;
        unsigned : 1;
        unsigned UOWN : 1;
    };
    struct {
        unsigned : 2;
        unsigned PID : 4; 
        unsigned : 2;
    };
} STATUS;
void PrintIt() {
    printf("Size of UNION is %d \n", sizeof(STATUS));
}

It should be the largest of any member, which each member is only 8 bits.

The code that caught my eye and made me investigate this is:

STAT.BC9 = 0;
STAT.BC8 = 0;
STAT.Val |= byteToSend;

Which the third line merges into the values from the first and second.

So I wanted to test it, it's coming out as 4 bytes, not one. I even tested it in a few different compilers (hence the #pragma usage for MS Visual C).

Each member is exactly 8 bits, and the last two struct overlap to place the PID values in the same memory location. And yet this is 4 bytes every way I use a compiler to evaluate it.

Is there something in the behavior of adding structs to unions?

Any explanation is appreciated.

4 Answers

While not explicitly specified in the C standard, bitfield will typically occupy a unit corresponding to the base type they are declared with.

In this case all of the bitfields are declared as unsigned. This type is probably 4 bytes on your system so the bitfields occupy a unit of that type.

If you change the types of the fields to unsigned char or uint8_t they should take up only one byte. Note that this assumes your compiler allows using these types for bitfields, although most do.

C has the concept of implicit type. So unsigned will declare an unsigned int. But it gets weirder. The same goes if you only use const or static/auto.

const x = 5; // Declares a const int variable
static x;    // Declares a static int variable
const static unsigned x = 5; // Declares a const static unsigned int variable

What you want is an unsigned char.

It should be the largest of any member, which each member is only 8 bits.

It should be at least that size. Nothing prevents it from being bigger.

  1. It will not compile as you have duplicate member names in anonymous structures.
  2. You do need to pack it if you use 8 bits type for your bitfields.
typedef union 
{
    unsigned char Val;
    struct {
        unsigned char BC8 : 1;
        unsigned char BC9 : 1;
        unsigned char BSTALL : 1;
        unsigned char DTSEN : 1; 
        unsigned char INCDIS : 1;
        unsigned char KEN : 1;   
        unsigned char DTS : 1;   
        unsigned char UOWN : 1;  
    };
    struct {
        unsigned char BC81 : 1;
        unsigned char BC91 : 1;
        unsigned char PID0 : 1;
        unsigned char PID1 : 1;
        unsigned char PID2 : 1;
        unsigned char PID3 : 1;
        unsigned char : 1;
        unsigned char UOWN1 : 1;
    };
    struct {
        unsigned char : 2;
        unsigned char PID : 4; 
        unsigned char : 2;
    };
} STATUS;

int main(void) {
    printf("Size of UNION is %d \n", (int)sizeof(STATUS));
}

https://godbolt.org/z/zsfhnGeWd

Remember that some compilers will pack to the type you specify (chibicc for example).

While dbush was answering, I was adding my own answer as well, after digging deeper.

I would describe it as: The "unsigned" implies an "int" So you are defining the first 8 bits of an integer storage.

typedef union _STATUS {
BYTE Val;
struct {
    unsigned char BC8 : 1;
    unsigned char BC9 : 1;
    unsigned char BSTALL : 1;
    unsigned char DTSEN : 1; 
    unsigned char INCDIS : 1;
....
} STATUS;

When limiting the size to unsigned char, the union is now actually 1 byte.

Related