Reputation: 1013
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
Reputation: 67476
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
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
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
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