分类:
2008-03-12 21:31:12
假设MS1按如下方式内存布局(本文所有示意图中的内存地址从左至右递增):
_____________________________
| | |
| a | b |
| | |
+---------------------------+
Bytes: 1 4
因为MS1中有最强对齐要求的是b字段(int),所以根据编译器的对齐规则以及ANSI C标准,MS1对象的首地址一定是4(int类型的对齐模数)的倍数。那么上述内存布局中的b字段能满足int类型的对齐要求吗?嗯,当然不能。如果你是编译器,你会如何巧妙安排来满足CPU的癖好呢?呵呵,经过1毫秒的艰苦思考,你一定得出了如下的方案:
_______________________________________
| |\\\\\\\\\\\| |
| a |\\padding\\| b |
| |\\\\\\\\\\\| |
+-------------------------------------+
Bytes: 1 3 4
这个方案在a与b之间多分配了3个填充(padding)字节,这样当整个struct对象首地址满足4字节的对齐要求时,b字段也一定能满足int型的4字节对齐规定。那么sizeof(MS1)显然就应该是8,而b字段相对于结构体首地址的偏移就是4。非常好理解,对吗?现在我们把MS1中的字段交换一下顺序:
typedef struct ms2
{
int a;
char b;
} MS2;
或许你认为MS2比MS1的情况要简单,它的布局应该就是
_______________________
| | |
| a | b |
| | |
+---------------------+
Bytes: 4 1
因为MS2对象同样要满足4字节对齐规定,而此时a的地址与结构体的首地址相等,所以它一定也是4字节对齐。嗯,分析得有道理,可是却不全面。让我们来考虑一下定义一个MS2类型的数组会出现什么问题。C标准保证,任何类型(包括自定义结构类型)的数组所占空间的大小一定等于一个单独的该类型数据的大小乘以数组元素的个数。换句话说,数组各元素之间不会有空隙。按照上面的方案,一个MS2数组array的布局就是:
|<- array[1] ->|<- array[2] ->|<- array[3] .....
__________________________________________________________
| | | | |
| a | b | a | b |.............
| | | | |
+----------------------------------------------------------
Bytes: 4 1 4 1
当数组首地址是4字节对齐时,array[1].a也是4字节对齐,可是array[2].a呢?array[3].a ....呢?可见这种方案在定义结构体数组时无法让数组中所有元素的字段都满足对齐规定,必须修改成如下形式:
___________________________________
| | |\\\\\\\\\\\|
| a | b |\\padding\\|
| | |\\\\\\\\\\\|
+---------------------------------+
Bytes: 4 1 3
现在无论是定义一个单独的MS2变量还是MS2数组,均能保证所有元素的所有字段都满足对齐规定。那么sizeof(MS2)仍然是8,而a的偏移为0,b的偏移是4。
好的,现在你已经掌握了结构体内存布局的基本准则,尝试分析一个稍微复杂点的类型吧。
typedef struct ms3
{
char a;
short b;
double c;
} MS3;
我想你一定能得出如下正确的布局图:
padding
|
_____v_________________________________
| |\| |\\\\\\\\\| |
| a |\| b |\padding\| c |
| |\| |\\\\\\\\\| |
+-------------------------------------+
Bytes: 1 1 2 4 8
sizeof(short)等于2,b字段应从偶数地址开始,所以a的后面填充一个字节,而sizeof(double)等于8,c字段要从8倍数地址开始,前面的a、b字段加上填充字节已经有4 bytes,所以b后面再填充4个字节就可以保证c字段的对齐要求了。