关于内存地址对齐,尤其是struct中成员的对齐导致的struct的size问题很多人(包括我:()似乎都没有一个比较清晰的认识,所以产生了整理这方面思路和帖子的想法,下面的文字是资料、文档、实验和推测的混合体,有错误是肯定的:)。能给您提供一点帮助,是我最大的愿望。(有点麻了) 引: struct s {char c;int i;}; 在sizeof(char)=1 sizeof(int)=4的情况下sizeof(struct s)为什么经常是8不是5? 这个就是对齐(alignment)的缘故。 那么什么是对齐?现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变量的时候经常在特定的内存地址访问,这就是对齐。为什么呢?msdn for vc6中有这么一段: This principle is especially important when you write code for porting to multiple processors. A misaligned 4-byte data member, which is on an address that is not a multiple of four, causes a performance penalty with an 80386 processor and a hardware exception with a MIPS® RISC processor. In the latter case, although the system handles the exception, the performance penalty is significantly greater. 大意是:1.某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2.其余的硬件平台虽然可以在任何地址处取得任何类型的数据,但如果变量没有对齐的情况下,取这个数据可能有效率上的损失。 所以为了不出错或者优化,在访问特定变量的时候要在特定的内存地址访问,这也是很多时候管对齐叫优化对齐的缘故。普通情况下编译器负责做这件事情。对齐问题在移植的时候尤其需要考虑进去。 一、vc6和gcc3.3.4 i386(以下所说gcc均为这个版本)如何做这件事情的 1.基本类型的数据(int,double等)依编译器有不同的对齐策略 在vc6中基本类型的数据对齐在sizeof(基本类型的数据)上(以下说"对齐在N上"的意思就是:地址%N=0) 在gcc中char对齐在1上,short对齐在2上,int对齐在4上,double对齐在4上。 这里有一个概念:alignment-requirement(不知应该翻译成什么),对应的就是上面的N,即short的alignment-requirement是2,int的alignment-requirement是4 2.struct的基本类型的数据成员对齐和struct本身的对齐(比较复杂) 首先struct本身对齐:它的alignment-requirement值为成员的alignment-requirement中最大的值,举例 struct s{char c;int i;char z};struct s var1;在sizeof(int) = 4的情况下(&var1) % 4 = 0。 考虑声明struct数组的情况:struct s var2[10]; 为了保证var2[0]和var2[1]...var2[9]都对齐在4上,要保证sizeof(struct s) % 4 = 0。这就是说struct的size必须是它的alignment-requirement整倍数。 其次struct的成员变量也要对齐,即(&(var1.i)) % 4 = 0。 这样,假设&var1 = 1000,那么就可以推出 (&(var1.c)) = 1000 (&(var1.i)) = 1004 因为1001,1002,1003都无法被4整除。 (&(var1.z)) = 1008 现在var1已经占用了9个字节了,由于sizeof(struct s) % 4 = 0,所以sizeof(struct s) = 12。 var1.c后的1001,1002,1003,var1.z后的1009,1010,1011地址处的就叫做填充物,因为这几个地址和3个成员变量都没什么关系。 遗留问题:可以看到alignment-requirement都是2的整数幂,如果出现某种数据的alignment-requirement不是2的整数幂有可能出现什么事情呢?我推测struct的alignment-requirement值就得是成员的alignment- requirement的最小公倍数了。但现在没这个问题,因为最大的就是最小公倍数。 3.struct嵌套情况下对齐问题(有一点绕),比如: struct s2{char a;struct s v;char b};struct s2 var3; struct s2对齐仍然遵守它的alignment-requirement值为成员的alignment-requirement中最大的值,只不过形成递归, struct s的alignment-requirement为4,char的alignment-requirement为1,那么struct s2的alignment-requirement为4。 要注意的是v的alignment-requirement也要为4。假设&var3 = 1000 (&(var3.a)) = 1000 (&(var3.v)) = 1004 因为1001,1002,1003都无法被4整除。 (&(var3.v.c)) = 1004 (&(var3.v.i)) = 1008 (&(var3.v.z)) = 1012 (&(var3.b)) = 1016 因为sizeof(struct s) = 12 现在var3已经占用了17个字节了,由于sizeof(struct s2) % 4 = 0,所以sizeof(struct s2) = 20。 这样var3.a后的1001,1002,1003,var3.v.c后的1005,1006,1007,var3.v.z后的1013,1014,1015,var3.b后的1017,1018,1019地址处都是填充物。 比较一下struct s3{char a;char c;int i;char z;char b};可以看出s2和s3在对齐上是完全不同的。因为填充物在c后有2个字节,在b后有两个字节。 为什么呢,我想为了保证var3.v = var1这种式子的正确性。 4.union的对齐问题,为了凑个整:),它的alignment-requirement就是它的最大成员的alignment-requirement,注意不是最大成员的size。 不过理论上似乎仍然存在那个最小公倍数的问题,当然实际中也许并不存在。 二、对齐和ansi c99标准 在c99标准中对对齐提及的不多,如何对齐的策略基本依赖于实现(implement),就是编译器,而编译器是和硬件平台密切相关,所以间接的也和硬件平台相关。 在看c99手册时要注意一个问题:就是指针(pointer)对齐和指针类型(pointer type)对齐。打个比方: short *pc;所谓指针类型对齐的意思就是&pc%(short *这种指针类型的alignment-requirement) = 0。 而指针对齐是说pc%(short类型的alignment-requirement) = 0。 pc=2002代表指针对齐了,而&pc=2002代表指针类型不对齐(当然前提是short *的alignment-requirement = 4)。 c99中6.3.2.3 Pointers第5条和第7条说的是指针对齐,这两条说的是整数和指针转化的问题,转化过程中有可能出现不对齐事件,比如short *pi = 2001,这种行为就是undefined,因为一旦使用*pi,就发生不对齐的事件。在这两条中英文出现的是pointer aligned,按我的理解指的是指针对齐。 c99中6.2.5 Types第27条说的是指针类型对齐,任何void*和char* have the same alignment requirements,任何struct* have the same alignment requirements,任何union* have the same alignment requirements。在这条中英文出现的是pointer have the same alignment requirements,按我的理解是指针类型对齐。 我感觉自己似乎没大说明白,并且对6.2.5 Types第27条和6.3.2.3 Pointers第5条和第7条这种区别也不一定理解对,不过也许对您理解上有帮助。 三、#pragma pack预编译指令 也许vc6和gcc都提供的就是#pragma pack(n)了,n=1,2,4,8,16,这个n称为packing size。自然还有那个最小公倍数的理论可能性:)。 有人将这个n理解为要填充的字节的上限,一个成员要填充的字节#pragma pack(4) struct s4{char c1;char c2;double i;}; sizeof(struct s4) = 12,填充2个字节,如果按照填充的字节的上限的解释,填充的字节数要<4,那么也可以填充3个字节,以达到上限?我觉得还是应该按照对齐到4解释更容易理解。 有一个可能误解的地方是这个n是对所有类型来讲的么?连char也要4字节对齐么?不是,对alignment-requirement< packing size的类型,仍然按照alignment-requirement对齐,这样的话,在#pragma pack(4)情况下,short仍然对齐到2,char仍然对齐到1。 vc6中默认packing size是8,因为没什么类型的alignment-requirement>8,所以默认下就是按照alignment-requirement对齐。 gcc的默认packing size具体不清楚,通过实验推出packing size>=8。 四、gcc下的double的alignment-requirement 在用编译选项-malign-double的时候,double的alignment-requirement是双字(32位机器上就是8),用-mno -align-double的时候,double的alignment-requirement是单字。在我的机器上没任何选项的时候double的 alignment-requirement是单字。 这个编译选项只针对i386和x86-64,并且对long double和long long数据类型也适用。 关于对齐的知识应该很多,进一步的认识可能要凭经验了。 |