7

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.

SpacemanScott
  • 953
  • 9
  • 21

4 Answers4

6

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.

dbush
  • 205,898
  • 23
  • 218
  • 273
5

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.

klutt
  • 30,332
  • 17
  • 55
  • 95
1
  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).

Matthew Read
  • 1,365
  • 1
  • 30
  • 50
0___________
  • 60,014
  • 4
  • 34
  • 74
0

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.

SpacemanScott
  • 953
  • 9
  • 21
  • As always happens, typing questions makes you think about it more, and look further. I'd take it down, except it might help someone else with a similar question. – SpacemanScott Sep 13 '21 at 13:57
  • Your question got upvoted and you've gotten answers to it so I think others _will_ benefit from the question still being visible. You could also accept one of the answers you've gotten. – Ted Lyngmo Sep 13 '21 at 14:02
  • @TedLyngmo Thx. I was the first upvote to dbush's answer, while composing my own answer. – SpacemanScott Sep 14 '21 at 15:10