关于内存地址对齐,尤其是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理解为要填充的字节的上限,一个成员要填充的字节<n。我觉得还是应该理解为对齐到n,因为在vc6中对
#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数据类型也适用。
关于对齐的知识应该很多,进一步的认识可能要凭经验了。
|