版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、结构体成员的内存分布与对齐马国峻maguojun2005sinamaguojun2005sina我们先看一道旧M和微软的笔试题: 旧 M 笔试题: struct(short a1;short a2;short a3;A; struct( long a1;short a2;B;sizeof( A)=6,注:sizeof(short)=2,sizeof(long)=4微软笔试题:struct example1(short a ;long b;struct example2(char c;example1 struct1;short e;int main(int argc, char* argv)(
2、example2 e2;int d=(unsigned int)&e2.struct1-(unsigned int)&e2.c;printf(%d,%d,%dn,sizeof(example1),sizeof(example2),d);return 0;输出结果?要能活除的分析上面的问题就要搞活楚结构体变量的成员在内存里是如何分布的、成员先后顺序 是怎样的、成员之间是连续的还是分散的、还是其他的什么形式?其实这些问题既和软件相关乂和硬 件相关。所谓软件相关主要是指和具体的编程语言的编译器的特性相关,编译器为了优化CPU访问内存的效率,在生成结构体成员的起始地址时遵循着某种特定的
3、规那么,这就是所谓的结构体成员“对齐;所谓硬件相关主要是指CPU的“字节序问题,也就是大于一个字节类型的数据如int类型、short类型等,在内存中的存放顺序,即单个字节与上下地址的对应关系。字节序分为两类:Big-Endian和Little-Endian ,有的文章上称之为“大端和“小端,他们是这样定义的:Little-Endian就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端;Big-Endian就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。Intel、VAX Unisys处理器的计算机中的数据的字节顺序是Little-Endian , IBM大型机和大多
4、数Unix平台的计算机中字节顺序是Big Endian。关与Big-Endian和Little-Endian问题本文暂不做详细讨论,本文将以小端机(此处为intel x86架 构的计算机)、OS: WindowsXp和VC+6.0编译器来详细讨论结构体成员的“对齐问题。前面说了,为了优化CPU访问内存的效率,程序语言的编译器在做变量的存储分配时就进行了分配 优化处理,优化规那么大致原那么是这样:对于n字节的元素(n=2,4,8,.),它的首地址能被n整除,这种原那么称为“对齐,如WORD(2字 节)的值应该能被2整除的位置,DWORD (4字节)应该在能被4整除的位置。对于结构体来说,结构体的
5、成员在内存中顺序存放,所占内存地址依次增高,第一个成员处于低地址处,最后一个成员处丁最高地址处,但结构体成员的内存分配不一定是连续的,编译器会对其成 员变量依据前面介绍的“对齐原那么进行处理。对待每个成员类似丁对待单个n字节的元素一样,依次为每个元素找一个适合的首地址,使得其符合上述的“对齐原那么。通常编译器中可以设置一个对 齐参数n,但这个n并不是结构体成员实际的对齐参数,VC+6.0中结构体的每个成员实际对齐参数N通常是这样计算得到的N=min(sizeof(该成员类型),n)(n为VC+6.0中可设置的值)。成员的内存分配规律是这样的: 从结构体的首地址开始向后依次为每个成员寻找第一个满
6、足条件 的首地址x,x,该条件是x x%N N= 0,并且整个结构的长度必须为各个成员所使用的对齐参数中最大的 那个值的最小整数倍,不够就补空字节。sizeof(B)=8,为什么?结构体中所有成员的对齐参数N的最大值称为结构体的对齐参数。VC+6.0中n默认是8个字节,可以修改这个设定的对齐参数,方法为在菜单“工程的“设置 中的“C/C+选项卡的“分类中“Code Generation的“Struct member alignment 中设置,Ibyte、2byte、4byte、8byte、16byte等几种,默认为8byte也可以程序控制,采用指令:#pragma pack(xx)控制如#p
7、ragma pack(1), 1字节对齐,#pragma pack(4), 4字节对齐#pragma pack(16), 16字节对齐接下来我们将分不同的情况来详细讨论结构体成员的分布情况,顺便提醒一下,常见类型的长度:Int4byte,Short 2byte,Char 1byte,Double 8byte,Long 4byte让我们先看下例:struct Acharc;/1bytedoubled;/8byteshorts;/2byteinti;/4byte;int main(int argc, char* argv)A strua;printf(%len:dn,sizeof(A);print
8、f(%d,%d,%d,%d,&strua.c,&strua.d,&strua.s,&strua.i);return 0;1) n设置为8byte时结果:len:24,1245032,1245040,1245048,1245052内存中成员分布如下:4byte4byteStrua-/ *c补。1245032d1245040-s补。i1245048-strua.c分配在一个起始丁8的整数倍的地址1245032(为什么是这样读者先自己思考,读完就会明白), 接下来要在strua.c之后分配strua.d,由丁double为8字节,取N=min(8,8), 8字节来对齐
9、,所以从strua.c向后找第一个能被8整除的地址,所以取1245032+8得1245040, strua.s为2byte小丁参数n,所以N=min(2,8),即N=2,取2字节长度对齐,所以要从strua.d后面寻找第一个能被2整除的地址来存储strua.s,由丁strua.d后面的地址为1245048可以被2整除,所以strua.s紧接着分配,现在来分配strua.i,int为4byte,小丁指定对齐参数8byte,所以N=min (4, 8)取N=4byte对齐,strua.s后面第一个能被4整除地址为1245048+4,所以在1245048+4的位置分配了strua.i,中间补空, 同
10、时由丁所有成员的N值的最大值为8,所以整个结构长度为8byte的最小整数倍, 即取24byte其余均补0.丁是该结构体的对齐参数就是8byte。2)当对齐参数n设置为16byte时,结果同上,不再分析3)当对齐参数设置为4byte时上例结果为:Len:201245036,1245040,1245048,1245052内存中成员分布如下:4byteStrua_c补。12450361245040d1245048*s补。1245052_iStrua.c起始丁一个4的整数倍的地址,接下来要在strua.c之后分配strua.d,由丁strua.d长度为8byte,大丁对齐参数4byte,所以N=min
11、 (8, 4)取最小的4字节, 所以向后找第一个能被4整除的地址来作 为strua.d首地址, 故取1245036+4,接着要在strua.d后分配strua.s, strua.s长度为2byte小丁4byte,取N=min (2, 4) 2byte对齐,由丁strua.d后的地址为1245048可以被2整除,所以直接在strua.d后面分配,strua.i的长度为4byte,所以取N=min (4, 4) 4byte对齐,所以 从strua.s向后找第一个能被4整除的位置即1245048+4来分配和strua.i,同时N的最大值为4byte,所 以整个结构的长度为4byte的最小整数倍16b
12、yte4)当对齐参数设置为2byte时上例结果为:Len:161245040,1245042,1245050,1245052Strua.c1245040Strua.c分配后,向后找一第一个能被2整除的位置来存放strua.d依次类推5) Ibyte对齐时:上例结果为:Len:151245040,1245041,1245049,12450512bvte此时,N=min (sizeof(成员),1),取N=1,由丁1可以整除任何整数,所以各个成员依次分配,没 有问空,如下列图所示:1byte8byte2byte 4byte- -6)当结构体成员为数组时,并不是将整个数组当成一个成员来对待,而是将数
13、组的每个元素当一个成 员来分配,其他分配规那么不变,如将上例的结构体改为 :struct Acharc;doubleshortchar/1byted; /8bytes; /2byteszBuf5;;对齐参数设置为8byte,贝U,运行结果如下:Len:241245032,1245040,1245048,12450508bytes1szBufStrua的s分配后,接下来分配Strua的数组szBuf5,这里要单独分配它的每个元素,由丁是char类 型,所以N=min(1,8),取N=1,所以数组szBuf5的元素依次分配没有间隙。7)当结构中有成员不是一个完整的类型单元,如int或short型,
14、而是该类型的一段时,即位段时,如struct Aintinta1:5;a2:9;charintc;b:4;short s;;对丁位段成员,存储是按其类型分配空间的,如int型就分配4个连续的存储单元,如果是相邻的同类型的段位成员就连续存放,共用存储单元,此处如a1,a2将公用一个4字节的存储单元,当该 类型的长度不够用时,就另起一个该类型长度的存储空间。有位段时的对齐规那么是这样:同类型的、 相邻的可连续在一个类型的存储空间中存放的位段成员作为一个该类型的成员变量来对待,不是同类 型的、相邻的位段成员,分别当作一个单独得该类型的成员来对待,分配一个完整的类型空间,其长 度为该类型的长度,其他成
15、员的分配规那么不变,仍然按照前述的对齐规那么进行。对丁struct A,VC+6.0中n设置为8时,sizeof(A)=16,内存分布:乂如:struct Bint a:5;int b:7;int c:6;int d:9;char e:2;int x;Vc+6.0的对齐参数设置为8、16、4字节对齐时,sizeof(A)=12内存分布为:(灰色局部未使用)当对齐参数设置为2字节时:(灰色局部未使用)sizeof(A)=10乂如intel的笔试题:#include stdafx.h #includestruct bit int a:3;int b:2;int c:3;;int main(inta
16、rgc, char* argv)(bit s;char *c = (char*)&s;*c = 0 x99;cout s.a endl s.bendls.cendl;return 0;Output:?运行的结果是1-1-4结构bit的成员在内存中由低地址到高地址顺序存放,执行*c=0 x99;后成员的内存分布情况为:a bcc10011001&s8当结构体成员是结构体类型时,那么该过程是个递归过程,且把该成员作为一个整体来对待,如微 软笔试题:struct example1short a ;long b;struct example2(char c;example1 struc
17、t1;short e;int main(int argc, char* argv)(example2 e2;int d=(unsigned int)&e2.struct1-(unsigned int)&e2.c;printf(%d,%d,%dn,sizeof(example1),sizeof(example2),d);return 0;8byte对齐时,结果为:8, 16, 4内存分布为:因为example 1的对齐参数为4,分配完c后要接着分配struct1,这时的对齐参数为minstruct 1的对齐example2example1c补0a补0be补04byte参数,指定对
18、齐参数,开始分配structl,在struct 1的成员分配过程中乂是按照前述的规那么来分配的。关于结构体内存对齐内存对齐应该是编译器的 管辖范围。编译器为程序中的每个 数据单元安排在适当的位置上。但是C语言的一个 特点就是太灵活,太强大,它允许你干预内存对齐。如果你想了解更加底层的秘密,内存对齐对你就不应该再透明了。一、 内存对齐的原因大局部的参考资料都是如是说的:1、 平台原因移植原因:不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处 取某些特定类型的数据,否那么抛出硬件异常。2、 性能原因:数据结构尤其是栈应该尽可能地在自然边界上对齐。原因在于,为了访问未对
19、齐的内存,处理器需 要作两次内存访问;而对齐的内存访问仅需要一次访问。二、 对齐规那么每个特定平台上的编译器都有自己的默认对齐系数也叫对齐模数。程序员可以通过预编译命令#pragmapackn , n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的 对齐系数。对齐步骤:1、 数据成员对齐规那么:结构struct或联合union的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员的对齐 按照#pragma pack指定的数值和这个数据成员自身长度中,比拟小的那个进行。2、 结构或联合的整体对齐规那么:在数据成员完成各自对齐之后,结构或联合本身也要进行对齐,对齐将 按
20、照#pragma pack指定的数值和结构或联合最大数据成员长度中,比拟小的那个进行。3、 结合1、2颗推断:当#pragma pack的n值等于或超过所有数据成员长度的时候,这个n值的大小将不产生 任何效果。备注:数组成员按长度按数组类型长度计算,如char t9,在第1步中数据自身长度按1算,累加结构体时长度为9;第2步中,找最大数据长度时,如果结构体T有复杂类型成员A的,该A成员的长度为该复杂类型成员A的最大成员长度。三、 试验我们通过一系列例子的详细说明来证明这个规那么吧!我试验用的编译器包括GCC 3.4.2和VC6.0的C编译器,平台为Windows XP + Sp2。我们将用典型
21、的struct对齐来说明。首先我们定义一个struct :#pragma packn /* n = 1,2, 4, 8, 16 */struct test_t int a;char b;short c;char d;#pragma pack(n)首先我们首先确认在试验平台上的各个类型的size ,经验证两个编译器的输出均为:sizeof(char) = 1sizeof(short) = 2sizeof(int) = 4我们的试验过程如下:通过#pragma pack(n)改变对齐系数,然后观察sizeof(struct test_t)1、1字节对齐(#pragma pack(1)输出结果:si
22、zeof(struct test_t) = 8 两个编译器输出一致分析过程:1)成员数据对齐#pragma pack(1)struct test_t (int a; /*长度4 1按1对齐;起始offset=5 5%1=0;存放位置区间5,6 */char d; /*长度1 = 1按1对齐;起始offset=7 7%1=0;存放位置区间7 */;#pragma pack()成员总大小=82)整体对齐整体对齐系数=min(max(int,short,char), 1) = 1整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=8 /* 8%1=0 */ 注12、2字节对齐(#prag
23、ma pack(2)输出结果:sizeof(struct test_t) = 10 两个编译器输出一至分析过程:1)成员数据对齐#pragma pack(2)struct test_t (int a; /*长度4 2按2对齐;起始offset=0 0%2=0;存放位置区间0,3 */char b; /*长度1 2按1对齐;起始offset=4 4%1=0;存放位置区间4 */short c; /*长度2 = 2按2对齐;起始offset=6 6%2=0;存放位置区间6,7 */char d; /*长度1 2按1对齐;起始offset=8 8%1=0;存放位置区间8 */;的值。#pragma
24、pack()成员总大小=92整体对齐整体对齐系数=min(max(int,short,char), 2) = 2整体大小size=$成员总大小按$整体对齐系数圆整3、4字节对齐(#pragma pack(4)输出结果:sizeof(struct test_t) = 12 两个编译器输出= 10 /* 10%2=0 */分析过程:1成员数据对齐#pragma pack(4)struct test_t int a; /*长度4 = 4按4对齐;起始offset=0 0%4=0char b; /*长度按1对齐;起始offset=4 4%1=0short c; /*长度按2对齐;起始offset=6
25、6%2=0char d; /*长度按1对齐;起始offset=8 8%1=00,3 */;存放位置区间4 */;存放位置区间6,7 */;存放位置区间8 */;#pragma pack()成员总大小=92整体对齐整体对齐系数=min(max(int,short,char), 4) = 4整体大小size=$成员总大小按$整体对齐系数圆整4、8字节对齐(#pragma pack(8)输出结果:sizeof(struct test_t) = 12 两个编译器输出= 12 /* 12%4=0 */分析过程:1成员数据对齐#pragma pack(8)struct test_t int a; /*长度
26、4 8按4对齐;起始offset=0 0%4=0char b; /*长度按1对齐;起始offset=4 4%1=0short c; /*长度按2对齐;起始offset=6 6%2=0char d; /*长度按1对齐;起始offset=8 8%1=00,3 */;存放位置区间4 */;存放位置区间6,7 */;存放位置区间8 */;#pragma pack()成员总大小=92)整体对齐整体对齐系数=min(max(int,short,char), 8) = 4整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=12 /* 12%4=0 */5、16字节对齐(#pragma pack(
27、16)输出结果:sizeof(struct test_t) = 12 两个编译器输出一致分析过程:1)成员数据对齐#pragma pack(16)struct test_t int a; /*长度4 16按4对齐;起始offset=0 0%4=0;存放位置区间0,3 */char b; /*长度1 16按1对齐;起始offset=4 4%1=0;存放位置区间4 */short c; /*长度2 16按2对齐;起始offset=6 6%2=0;存放位置区间6,7 */char d; /*长度1 16按1对齐;起始offset=8 8%1=0;存放位置区间8 */;#pragma pack()成员
28、总大小=92)整体对齐整体对齐系数=min(max(int,short,char), 16) = 4整体大小(size)=$(成员总大小)按$(整体对齐系数)圆整=12 /* 12%4=0 */记录类型的内存分配!Packed Record和Record的不同之处!typeMyRec=Recordvar1:integer;var2,var3,var4,var5,var6,var7,var8:shortint;var9:integer;var10:shortint;var11:integer;var12,var13:shortint;end;ShowMessage(intTostr(SizeOf
29、(MyRec);结果显示为18,而按我想象应为16。请高手讲解一下Delphi5.0中变量内存空间分配机制,因为我有一个数组MyArray:Array1.1000000 of MyRec;需要考虑节省内存问题,另外不要说我懒不爱看书,我手头所有关于Delphi的书都没有提到这个问题。答复:显示的结果应该为28,而不是18 !按道理应该是22。用Packed的结果就是22。拟定义的数组比拟大,应该用packed record !原因如下:在Windows中内存的分配一次是4个字节的。而Packed按字节进行内存的申请和分配,这样速度要慢一些,因为需要额外的时间来进行指针的定位。因此如果不用Pac
30、ked的话,Delphi将按一次4个字节的方式申请内存,因此如果一个变量没有4个字节宽的话也要占4个字节!这样就浪费了。按上面的例子来说:var1:integer;/integer刚好4个字节!var2-var5占用4个字节,Var6-Var8占用4个字节,浪费了一个字节。var9:integer/占用4个字节;var10:占用4个字节;浪费3个字节var11:占用4个字节;var12,var13占用4个字节;浪费2个字节所以,如果不用packed的话,那么一共浪费6个字节!所以原来22个字节的记录需要28个字节的内存空间!*回复人:eDRIVE(eDRIVE) (2001-3-2 17:45
31、:00)得0分这是因为在32位的环境中,所有变量分配的内存都进行“边界对齐造成的。这样做可以对速度有优化作用;但是单个定义的变量至少会占用32位,即4个字节。所以会有长度误差,你可以用packed关键字取消这种优化。深入的分析,内存空间(不是内存地址)在计算机中划分为无数与总线宽度一致的单位,单位之间相接的地方称为“边界;总线在对内存进行访问时,每次访问周期只能读写一个单位(32bit ),如果一个变量横跨“边界的话, 那么读或写这个变量就得用两个访问周期,而“边界对齐时,只需一个访问周期,速度当然会有所优化。Record的数据各个字节都是对齐的,数据格式比拟完整,所以这种格式相对但是因为格式
32、比拟整齐,所以电脑读取这个类型的数据的时候速度比拟快。而Packed Record对数据进行了压缩,节省了内存空间,当然他的速度也变的慢了。typepacked占用的内存比拟大,/Declarean unpackedrecordvarTDefaultRecordRecordend;/namelfloatername2intDeclareTPackedRecordname1floatername2intend;defaultRecpackedRecstring4;single;char;Integer;packedPackedstring4;single;char;Integer;TDefaul
33、tRecord;recordRecordTPackedRecord;beginShowMessage(Defaultrecordsize+IntToStr(SizeOf(defaultRec);ShowMessage(Packedrecordsize+IntToStr(SizeOf(packedRec);end;不过,对于现在的操作系统来,packed Record节省的那些空间已不用考虑他了。除了做DLL不用packed容易造成内存混乱和做硬件编程时比方串口编程时必须用到packed Record,其它情况都可以用RecordC的结构体与Delphi中的记录类型Object Pascal的指
34、针一、类型指针的定义。对于指向特定类型的指针,在C中是这样定义的:int *ptr;char *ptr;与之等价的Object Pascal是如何定义的呢?varptr: Integer;ptr:Achar;其实也就是符号的差异而已。二、无类型指针的定义。C中有void *类型,也就是可以指向任何类型数据的指针。Object Pascal为其定义了一个专门的类型:Pointer。于是,ptr : Pointer;就与C中的void *ptr;等价了。三、 指针的解除引用。要解除指针引用即取出指针所指区域的值,C的语法是*ptr , Object Pascal那么是ptrA。四、取地址指针赋值。
35、取某对象的地址并将其赋值给指针变量,C的语法是ptr = &Object;Object Pascal那么是ptr := Object;也只是符号的差异而已。五、指针运算。在C中,可以对指针进行移动的运算,如:char a20;char *ptr=a;ptr+;ptr+=2;当执行ptr+;时,编译器会产生让ptr前进sizeofchar步长的代码,之后,ptr将指向a1cptr+=2;这句使得ptr前进两个sizeofchar大小的步长。同样,我们来看一下Object Pascal中如何 实现:vara : array 1.20 of Char; ptr : PChar; /PChar
36、可以看作ACharbeginptr:=a;Inc(ptr);/这句等价于C的ptr+;Inc(ptr,2); /这句等价于C的ptr+=2;end;Defaultrecordsize20Packed recordsize =14六、 动态内存分配。C中,使用malloc()库函数分配内存,free()函数释放内存。如这样的代码:int *ptr, *ptr2;int i;ptr = (int*) malloc(sizeof(int) * 20);ptr2 = ptr;for(i=0;i20;i+)*ptr = i;ptr+;free(ptr2);Object Pascal中,动态分配内存的函数
37、是GetMem(),与之对应的释放函数为FreeMem()(传统Pascal中获取内存的函数是New()和Dispose(),但New()只能获得对象的单个实体的内存大小, 无法取得连续的存放多个对象的内存块)。因此,与上面那段C的代码等价的Object Pascal的代码为:varptr, ptr2 : integer;i: integer;beginGetMem(ptr, sizeof(integer) * 20);/这句等价于C的ptr = (int*) malloc(sizeof(int) * 20);ptr2 := ptr; /保存原始指针位置for i := 0 to 19 dobeginptrA:= i;Inc(ptr);end;FreeMem(ptr2);end;对于以上这个例子(无论是C版本的,还是Object Pascal版本的),都要注意一个问题,就是分配内存的单位是字节(BYTE),因此在使用GetMem时,其第二个参数如果想当然的写成20,那么就会出问题了(内存访问越界)。因为GetMem(ptr, 20);实际只分配了20个字节的内存空间,而 一个整形的大小是四个字节,那么访问第五个
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 【初中数学课件】圆复习课件
- 地心游记美术课件
- 2024年新高一物理初升高衔接《匀变速直线运动的位移与时间的关系》含答案解析
- 《欧亚商场安全培训》课件
- 练身手榴弹产业深度调研及未来发展现状趋势
- 治疗呼吸系统疾病的药物制剂产业深度调研及未来发展现状趋势
- 碳酸水用虹吸瓶市场洞察报告
- 医用热疗灯市场发展现状调查及供需格局分析预测报告
- 模型用湿黏土市场发展预测和趋势分析
- 橡皮膏市场洞察报告
- (2024年)solidworks完整教程学习课程
- 放射性肠炎中炎症相关细胞因子的作用机制及靶向治疗
- 新能源汽车的市场价格变化趋势
- 如何有效应对学习中的困难和挑战
- 通信行业应急预案编制及管理培训实施方案
- 高血压的中医气功疗法:调节气息与身心平衡
- 三年级上册竖式计算练习300题及答案
- 《说话要算数》示范课件第1课时
- 《第十二课 蛋炒饭》参考课件
- 2024年心理学考研真题及参考答案
- 低纤维蛋白原血症的护理查房
评论
0/150
提交评论