结构体的内存布局依赖于CPU、操做系统、编译器及编译时的对齐选项。结构体内部成员的对齐要求,结构体自己的对齐要求。最重要的有三点html
(一)成员对齐。对于结构体内部成员,一般会有这样的规定:各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。可是也能够看到,有时候某些字段若是严格按照大小紧密排列,根本没法达到这样的目的,所以有时候必须进行padding。各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节编译器会自动填充也就是padding。程序员
(二)而后,还要考虑整个结构体的对齐需求。ANSI C标准规定结构体类型的对齐要求不能比它全部字段中要求最严格的那个宽松,能够更严格。实际上要求结构体至少是其中的那个最大的元素大小的整数倍。由于有时候咱们使用的是结构体数组,因此结构体的大小还得保证结构体数组中各个结构体知足对齐要求,同时独立的结构体与结构体数组中单个结构体的大小应当是一致的。算法
(三)编译器的对齐指令。VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种状况:第1、若是n大于等于该变量所占用的字节数,那么偏移量必须知足默认的对齐方式,第2、若是n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用知足默认的对齐方式。结构的总大小也有个约束条件,分下面两种状况:若是n大于全部成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数。数组
规则:
一、数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,之后每一个数据成员的对齐按照#pragma pack指定的数值和这个数据成员自身长度中,比较小的那个进行。
二、结构(或联合)的总体对齐规则:在数据成员完成各自对齐以后,结构(或联合)自己也要进行对齐,对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中,比较小的那个进行。
三、结合一、2推断:当#pragma pack的n值等于或超过全部数据成员长度的时候,这个n值的大小将不产生任何效果。架构
总结一下:
成员对齐有一个重要的条件,即每一个成员分别对齐.即每一个成员按本身的方式对齐.若是有#pragma pack(8),它虽然指定了按8字节对齐,但并非全部的成员都是以8字节对齐.其对齐的规则是,每一个成员按类型的对齐参数(一般是这个类型的大小)和指定对齐参数(这里是8字节)中较小的一个对齐.而且结构的长度必须为所用过的全部对齐参数的整数倍,不够就补空字节.也就是说对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时能够保证每一项都边界对齐。实际上根据这些规则安排整个的内存布局的算法很简单,假设起始地址为0,开始安放第1个成员,而后找到下一个成员能够安放的起始位置,首先这个位置确定在第一个成员以外,其次知足那些对齐因素。找到知足这两个条件的第一个位置便可。而后再考虑下一个成员,逐次进行下去。最后再考虑整个结构体的对齐因素,肯定整个结构体的结束位置,这个位置的下个位置也就是下一个结构体的开始位置,保证它可以知足对齐。布局
好比:post
struct MyStruct { char dda; double dda1; int type }; (简单说明) struct MyStruct { char dda;//偏移量为0,知足对齐方式,dda占用1个字节; double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8 //的倍数,须要补足7个字节才能使偏移量变为8(知足对齐 //方式),所以VC自动填充7个字节,dda1存放在偏移量为8 //的地址上,它占用8个字节。 int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍 //数,知足int的对齐方式,因此不须要VC自动填充,type存 //放在偏移量为16的地址上,它占用4个字节。 };//全部成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构 //的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof //(double)=8)的倍数,因此须要填充4个字节,以知足结构的大小为 //sizeof(double)=8的倍数。 因此该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有听任何有意义的东西。
为什么要内存对齐性能
http://www.ibm.com/developerworks/library/pa-dalign/大数据
由于处理器读写数据,并非以字节为单位,而是以块(2,4,8,16字节)为单位进行的。若是不进行对齐,那么原本只须要一次进行的访问,可能须要好几回才能完成,而且还要进行额外的merger或者数据分离。致使效率低下。更严重地,会由于cpu不容许访问unaligned address,就会报错,或者打开调试器或者dump core,好比sun sparc solaris绝对不会容忍你访问unaligned address,都会以一个core结束你的程序的执行。因此通常编译器都会在编译时作相应的优化以保证程序运行时全部数据都是存储在'aligned address'上的,这就是内存对齐的由来。优化
在'Data alignment: Straighten up and fly right'这篇文章中做者还得出一个结论那就是:"若是访问的地址是unaligned的,那么采用大粒度访问内存有可能比小粒度访问内存还要慢"。
位域
若是结构体中含有位域(bit-field),那么VC中准则又要有所更改:
1) 若是相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 若是相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将重新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 若是相邻的位域字段的类型不一样,则各编译器的具体实现有差别,VC6采起不压缩方式(不一样位域字段存放在不一样的位域类型字节中),Dev-C++和GCC都采起压缩方式;
备注:当两字段类型不同的时候,对于不压缩方式,例如:
struct N
{
char c:2;
int i:4;
};
依然要知足不含位域结构体内存对齐准则第2条,i成员相对于结构体首地址的偏移应该是4的整数倍,因此c成员后要填充3个字节,而后再开辟4个字节的空间做为int型,其中4位用来存放i,因此上面结构体在VC中所占空间为8个字节;而对于采用压缩方式的编译器来讲,遵循不含位域结构体内存对齐准则第2条,不一样的是,若是填充的3个字节能容纳后面成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间,因此上面结构体N在GCC或者Dev-C++中所占空间应该是4个字节。
4) 若是位域字段之间穿插着非位域字段,则不进行压缩;
备注:
结构体
typedef struct
{
char c:2;
double i;
int c2:4;
}N3;
在GCC下占据的空间为16字节,在VC下占据的空间应该是24个字节。
5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
看一段引用
首先,至少有一点能够确定,那就是ANSI C保证结构体中各字段在内存中出现的位置是随它们的声明顺序依次递增的,而且第一个字段的首地址等于整个结构体实例的首地址。这时,有朋友可能会问:"标准是否规定相邻字段在内存中也相邻?"。唔,对不起,ANSI C没有作出保证,你的程序在任什么时候候都不该该依赖这个假设。那这是否意味着咱们永远没法勾勒出一幅更清晰更精确的结构体内存布局图?哦,固然不是。不过先让咱们从这个问题中暂时抽身,关注一下另外一个重要问题————内存对齐。
许多实际的计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的值是某个数k(一般它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。当一种类型S的对齐模数与另外一种类型T的对齐模数的比值是大于1的整数,咱们就称类型S的对齐要求比T强(严格),而称T比S弱(宽松)。这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来能够提高读取数据的速度。好比这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只须要一次内存操做。不然,咱们就可能须要两次内存操做才能完成这个动做,由于数据或许刚好横跨在两个符合对齐要求的8字节内存块上。某些处理器在数据不知足对齐要求的状况下可能会出错,可是Intel的IA32架构的处理器则无论数据是否对齐都能正确工做。不过Intel奉劝你们,若是想提高性能,那么全部的程序数据都应该尽量地对齐。Win32平台下的微软C编译器(cl.exe for 80x86)在默认状况下采用以下的对齐规则: 任何基本数据类型T的对齐模数就是T的大小,即sizeof(T)。好比对于double类型8字节),就要求该类型数据的地址老是8的倍数,而char类型数据(1字节)则能够从任何一个地址开始。
Linux下的GCC奉行的是另一套规则(在资料中查得,并未验证,如错误请指正):任何2字节大小(包括单字节吗?)的数据类型(好比short)的对齐模数是2,而其它全部超过2字节的数据类型(好比long,double)都以4为对齐模数。
如今回到咱们关心的struct上来。ANSI C规定一种结构类型的大小是它全部字段的大小以及字段之间或字段尾部的填充区大小之和。嗯?填充区?对,这就是为了使结构体字段知足内存对齐要求而额外分配给结构体的空间。那么结构体自己有什么对齐要求吗?有的,ANSI C标准规定结构体类型的对齐要求不能比它全部字段中要求最严格的那个宽松,能够更严格(但此非强制要求,VC7.1就仅仅是让它们同样严格)。
在实际开发中,咱们能够经过指定/Zp编译选项来更改编译器的对齐规则。好比指定/Zpn(VC7.1中n能够是一、二、四、八、16)就是告诉编译器最大对齐模数是n。在这种状况下,全部小于等于n字节的基本数据类型的对齐规则与默认的同样,可是大于n个字节的数据类型的对齐模数被限制为n。事实上,VC7.1的默认对齐选项就至关于/Zp8。仔细看看MSDN对这个选项的描述,会发现它郑重告诫了程序员不要在MIPS和Alpha平台上用/Zp1和/Zp2选项,也不要在16位平台上指定/Zp4和/Zp8(想一想为何?)。
##################################################################
参考文献:
再谈内存对齐问题- http://blog.ednchina.com/jasony/92132/message.aspx
也谈内存对齐- http://bigwhite.blogbus.com/logs/1347304.html