Many aspects of bitfields implementation are compiler dependent, and this creates some problem when packing values because the layout is undefined.
Anyway one main rule that the many compiler use is to differentiate between data types for packing. Meaning that is packed while in the same type, if the type change also fields start a new memory. I.e. if you change your declaration as:
struct s1
{
short a:9;
short b:4;
};
this will give you an output 0,2 as you would expect.
In the original declaration the compiler instantiate a short using only 9 bits, then instantiate a char, using only 4 bits, for the second variable.
This explain why you get a size of 3 bytes, but still don't explain why you cannot zero the variable value. So I decided to make a debug tour in Visual Studio going into assembly to understand the reason for the strange behavior, and I found it. The memory layout is:
+-+-+-+-+-+-+-+-+
|0|1|2|3|4|5|6|7|
+-+-+-+-+-+-+-+-+
\_______ _______/
v
+------+-----> a = 9bits: 8 from the lower one plus 1 from higher byte
^
+-+-+-+-+-+-+-+-+
|0|1|2|3|4|5|6|7| (bits from 1 to 7 not used)
+-+-+-+-+-+-+-+-+
+-+-+-+-+-+-+-+-+\
|0|1|2|3|4|5|6|7| \ (bits 4 to 7 not used)
+-+-+-+-+-+-+-+-+ |
\___ ___/ |
v > d = These 2 bytes are used for the short
b = 4 bits | in the union.
+-+-+-+-+-+-+-+-+ |
|0|1|2|3|4|5|6|7| /
+-+-+-+-+-+-+-+-+/
And this explain all.
When using the bit-fields you choose a type to define the memory space to use, that declare all variables that will share that space.
I.e.
struct MyStruct
{
char a:2;
char b:2;
char c:1;
char d:3;
int e:8;
int f:7;
int g:17;
}
Will occupy 5 bytes, the first for a, b, c, and d. Then the following int (4 bytes) will hold e, f and g.