SpacemanScott
SpacemanScott

Reputation: 1013

Why is this UNION like a struct?

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.

Upvotes: 7

Views: 629

Answers (4)

0___________
0___________

Reputation: 67476

  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).

Upvotes: 1

klutt
klutt

Reputation: 31306

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.

Upvotes: 5

SpacemanScott
SpacemanScott

Reputation: 1013

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.

Upvotes: 0

dbush
dbush

Reputation: 223739

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.

Upvotes: 6

Related Questions