许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。
为什么使用内存对齐,主要是用存储换取效率的问题:它简化了处理器与内存之间传输系统的设计,并且提升读取数据的速度,提高了执行效率。考察内存对齐问题多数出现在struct 和 union中。
Win32平台下的微软C编译器的对齐策略: 1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除; 备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。 2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding); 备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。 3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。 备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
下面一个示例程序(VS2008):
#include<stdio.h>struct type{char a;double d;short b;int c;};int main(){struct type t;t.a = 'a';t.b = 2;t.c = 3;t.d = 1.1;printf("type.a:%p /n",&(t.a));printf("type.b:%p /n",&(t.b));printf("type.c:%p /n",&(t.c));printf("type.d:%p /n",&(t.d));printf("sizeof(char):%d/n",sizeof(char));printf("sizeof(short):%d/n",sizeof(short));printf("sizeof(int):%d/n",sizeof(int));printf("sizeof(double):%d/n",sizeof(double));printf("sizeof(type):%d/n",sizeof(struct type));return 0;}
运行结果:
GNU GCC编译器中,遵循的准则有些区别:
对齐模数不是像上面所述的那样,根据最宽的基本数据类型来定。在GCC中,对齐模数的准则是:对齐模数最大只能是 4,也就是说,即使结构体中有double类型,对齐模数还是4,所以对齐模数只能是1,2,4。而且在上述的三条中,第2条里,offset必须是成员大小的整数倍,如果这个成员大小小于等于4则按照上述准则进行,但是如果大于4了,则结构体每个成员相对于结构体首地址的偏移量(offset)只能按照是4的整数倍来进行判断是否添加填充。
同样的代码,gcc编译器下:
#include<stdio.h>struct type{ char a; double d; short b; int c; //double d;};int main(){ struct type t; t.a='a'; t.b=2; t.c=3; t.d=1.1; printf("type.a:%p/n",&t.a); printf("type.b:%p/n",&t.b); printf("type.c:%p/n",&t.c); printf("type.d:%p/n",&t.d); printf("sizeof(char):%d/n",sizeof(char)); printf("sizeof(short):%d/n",sizeof(short)); printf("sizeof(int):%d/n",sizeof(int)); printf("sizeof(double):%d/n",sizeof(double)); printf("sizeof(type):%d/n",sizeof(struct type)); return 0;}
执行结果为:
root@ubuntu:~/cpp# ./testtype.a:0xbfb69e9ctype.b:0xbfb69ea8type.c:0xbfb69eactype.d:0xbfb69ea0sizeof(char):1sizeof(short):2sizeof(int):4sizeof(double):8sizeof(type):20root@ubuntu:~/cpp#
通过对比可以看出两者的区别。先人已有详尽的介绍,不再解释。
一种编程好习惯:
定义结构体和联合体时,占内存小的成员在前面,占内存大的成员在后面。这样可以节省内存空间。
位域类型(以前没听说过刚刚知道的):
C99规定int、unsigned int和bool可以作为位域类型,但编译器几乎都对此作了扩展,允许其它类型类型的存在,如char,short。
使用位域的主要目的是压缩存储,其大致规则为:1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式,Dev-C++采取压缩方式;4) 如果位域字段之间穿插着非位域字段,则不进行压缩;5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
下面的例子(VS2008)说明上述规则:
#include<stdio.h>struct type{char a : 3;char b : 4;char c : 5;int d:4;short e;short f:3;};int main(){struct type t;printf("type.a:%p /n",&t);printf("type.e:%p /n",&(t.e));printf("sizeof(char):%d/n",sizeof(char));printf("sizeof(short):%d/n",sizeof(short));printf("sizeof(int):%d/n",sizeof(int));printf("sizeof(double):%d/n",sizeof(double));printf("sizeof(type):%d/n",sizeof(struct type));return 0;}
运行结果:
type.a:0012FF58type.e:0012FF60sizeof(char):1sizeof(short):2sizeof(int):4sizeof(double):8sizeof(type):12
文字介绍部分多数非原创,详细参见:
http://blog.csdn.net/gulanglinux/archive/2009/07/24/4376691.aspx
http://www.yuanma.org/data/2006/1106/article_1781.htm