关于构造体中位域的存储

关于结构体中位域的存储
struct s1
{
int i: 8;
int j: 4;
int a: 3;
double b;
};
printf("sizeof(s1)= %d\n", sizeof(s1));
求输出结果.
答案:16
下面是解释:
理论上是这样的,首先是i在相对0的位置,占8位一个字节,然后,j就在相对一个字节
的位置,由于一个位置的字节数是4位的倍数,因此不用对齐,就放在那里了,然后是
a,要在3位的倍数关系的位置上,因此要移一位,在15位的位置上放下,目前总共是18
位,折算过来是2字节2位的样子,由于double是8 字节的,因此要在相对0要是8个字节
的位置上放下,因此从18位开始到8个字节之间的位置被忽略,直接放在8字节的位置
了,因此,总共是16字节。

我的问题:不明白这句"然后是a,要在3位的倍数关系的位置上,因此要移一位,在15位的位置上放下"是什么意思,a不是在j所在字节的第5位开始吗? 请高手帮忙解释解释,先谢了!

------解决方案--------------------
Win32平台下的微软C编译器(cl.exe for 80×86)的对齐策略: 
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 
备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。 
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 
备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 
备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
------解决方案--------------------
Specifies packing alignment for structure, union, and class members. 

 
#pragma pack( [ show ] | [ push | pop ] [, identifier ] , n )
 

Remarks
pack gives control at the data-declaration level. This differs from compiler option /Zp, which only provides module-level control. pack takes effect at the first struct, union, or class declaration after the pragma is seen; pack has no effect on definitions. Calling pack with no arguments sets n to its default value. This is equivalent to compiler option /Zp8.

Note that if you change the alignment of a structure, the structure will not use as much space in memory, but you may see a decrease in performance or even get a hardware-generated exception for unaligned access. It is possible to modify this exception behavior with SetErrorMode.

show (optional) 
Displays the current byte value for packing alignment. The value is displayed by means of a warning message.

push (optional) 
Puts a specified packing alignment value, n, on the internal compiler stack, and sets the current packing alignment value to n. If n is not specified, the current packing alignment value is pushed.

pop (optional) 
Removes the record from the top of the internal compiler stack. If n is not specified with pop, then the packing value associated with the resulting record on the top of the stack is the new packing alignment value. If n is specified, for example, #pragma pack(pop, 16), n becomes the new packing alignment value. If you pop with identifier, for example, #pragma pack(pop, r1), then all records on the stack are popped until the record with identifier is found, and that record is popped and the packing value associated with the resulting record on the top of is the stack the new packing alignment value. If you pop with an identifier that is not found in any record on the stack, then the pop is ignored.

identifier (optional) 
When used with push, assigns a name to the record on the internal compiler stack. When used with pop, pops records off the internal stack until identifier is removed; if identifier is not found on the internal stack, nothing is popped.

n(optional) 
Specifies the value, in bytes, to be used for packing. The default value for n is 8. Valid values are 1, 2, 4, 8, and 16. The alignment of a member will be on a boundary that is either a multiple of n or a multiple of the size of the member, whichever is smaller.

n can be used with push or pop for setting a particular stack value, or alone for setting the current value used by the compiler. 

#pragma pack(pop,identifier, n) is undefined.