为了正常的体验网站,请在浏览器设置里面开启Javascript功能!

字节对齐

2012-09-06 34页 doc 61KB 20阅读

用户头像

is_280370

暂无简介

举报
字节对齐ARM处理器下的内存对齐问题  2011-11-2222:39:52|  分类:嵌入式&ARM|  标签:|字号大中小 订阅from:MemoryAlignmentIssuesonARMProcessorshttps://brewx.qualcomm.com/bws/content/gi/common/appseng/en/knowledgebase/docs/kb95.html介绍   内存访问可以分为aligned和...
字节对齐
ARM处理器下的内存对齐问  2011-11-2222:39:52|  分类:嵌入式&ARM|  标签:|字号大中小 订阅from:MemoryAlignmentIssuesonARMProcessorshttps://brewx.qualcomm.com/bws/content/gi/common/appseng/en/knowledgebase/docs/kb95.html介绍   内存访问可以分为aligned和未对齐unaligned.对齐内存访问发生在数据分配在naturalsizeboundary,如果这个数据的大小是4bytes,而且它分配的地址可以被4整除,它就是分配在naturalsizeboundary的,它就是内存对齐的.未对齐内存访问就是其他的所有情况(内存地址不能被4整除);   ARM处理器被设计成可以高效的访问对齐数据,在ARM处理器上尝试访问未对齐内存数据将得到两种结果:错误数据或显著的执行差异(很快会讨论这些不同的表现).这不同于其他的CISC类型的处理器,它们可以正常的访问未对齐数据.   这篇文档将会描述一些对应用程序来说通用的方式处理未对齐内存访问和提供一些推荐的解决方案以解决这些问题.   症状   上述问题针对所有ARM架构的.然而,根据MMU是否使能和操作系统的支持,应用程序在不同的平台上有不同的表现.在默认情况下,未对齐内存访问不会被捕捉,而是返回一个错误数据.在使能了MMU的平台上,OS将可以捕捉未对齐内存访问而且在运行时调整正确.返回的结果将是正确的数据,但是将花费10-20个cpu周期.通常原因类型分配Code:voidmy_func(char*a){   int*b=(int*)a;   DBGPRINTF("%d",*b);}   这个简单的例子可以生成未对齐内存访问,因为我们不能保证参数char*a是在4字节边界上的.这样的类型定义在任何时候都应该避免.使用数据buffer   大多数常见的未对齐内存访问发生在错误的处理数据buffer,这些数据buffer可能包含任何从usb端口,网路,或文件中读取的数据.通常会设置这些数据为packed,意味着没有padding嵌入以确保buffer中的数据是naturalsizeboundary的.在这个例子中,我们将讨论装载从一个文件一个windowsBMP格式数据,然后解析其文件头的情况.   一个windowsBMP文件包含一个以下数据项的文件头,文件头由两个结构体组成:Code:typedefPACKEDstruct{   unsignedshortinttype;        /*Magicidentifier           */   unsignedintsize;               /*Filesizeinbytes         */   unsignedshortintreserved1,reserved2;   unsignedintoffset;             /*Offsettoimagedata,bytes*/}HEADER;typedefPACKEDstruct{   unsignedintsize;              /*Headersizeinbytes     */   intwidth,height;               /*Widthandheightofimage*/   unsignedshortintplanes;      /*Numberofcolourplanes  */   unsignedshortintbits;         /*Bitsperpixel           */   unsignedintcompression;        /*Compressiontype         */   unsignedintimagesize;          /*Imagesizeinbytes      */   intxresolution,yresolution;     /*Pixelspermeter         */   unsignedintncolours;           /*Numberofcolours        */   unsignedintimportantcolours;  /*Importantcolours        */}INFOHEADER;注意HEADER和INFOHEADER结构体的大小分别是14和40字节.假设我们想在程序运行是检测图片的宽带和高度,得到这些数据的代码如下:Code:#defineINFOHEADER_OFFSET(sizeof(HEADER))#defineWIDTH_OFFSET(INFOHEADER_OFFSET+offsetof(INFOHEADER,width))#defineHEIGHT_OFFSET(INFOHEADER_OFFSET+offsetof(INFOHEADER,height))intimageWidth,imageHeight;void*fileBuf;pMe->mFile=IFILEMGR_OpenFile(pMe->mFileMgr,"test.bmp",_OFM_READ);if(pMe->mFile){   IFILE_GetInfo(pMe->mFile,&fileInfo);   fileBuf=MALLOC(fileInfo.dwSize);   if(fileBuf)   {       result=IFILE_Read(pMe->mFile,fileBuf,fileInfo.dwSize);       if(result==fileInfo.dwSize)       {           imageWidth=*((uint32*)(((byte*)fileBuf)+WIDTH_OFFSET));           imageHeight=*((uint32*)(((byte*)fileBuf)+HEIGHT_OFFSET));       }   }}注意宽度和高度的偏移.因为它们位于一个half-wordboundary,以上面的代码访问它们的值将是未对齐内存访问.一些推荐的方式解决这个问题如下:推荐方案使用memcpy我们第一个选择是简单的使用memcpy将数据从buffer处理到我们的局部变量中:Code:if(result==fileInfo.dwSize){  MEMCPY(&imageWidth,         (((byte*)fileBuf)+WIDTH_OFFSET),         sizeof(uint32));  MEMCPY(&imageHeight,         (((byte*)fileBuf)+HEIGHT_OFFSET),         sizeof(uint32));}结果是内存被紧密的拷贝,避免了对齐问题.使用PACKED编译指令或者,我们可以使用PACKED编译指令以允许使用指针直接的访问我们想要的数据.也就是强制编译器处理对齐问题.在BREW环境下,PACKED定义如下:Code:#ifdef__ARMCC_VERSION   #definePACKED__packed#else   #definePACKED#endif通过标明一个指针是PACKED的,ARM编译器将始终生成合适的指令可以正确的访问内存.不管对齐,上边的例子的一个修改的版本,使用PACKED的指针,如下:Code:#defineINFOHEADER_OFFSET(sizeof(HEADER))#defineWIDTH_OFFSET(INFOHEADER_OFFSET+offsetof(INFOHEADER,width))#defineHEIGHT_OFFSET(INFOHEADER_OFFSET+offsetof(INFOHEADER,height))PACKEDuint32*pImageWidth;PACKEDuint32*pImageHeight;uint32imageWidth,imageHeight;void*fileBuf;pMe->mFile=IFILEMGR_OpenFile(pMe->mFileMgr,"test.bmp",_OFM_READ);if(pMe->mFile){   IFILE_GetInfo(pMe->mFile,&fileInfo);   fileBuf=MALLOC(fileInfo.dwSize);   if(fileBuf)   {       result=IFILE_Read(pMe->mFile,fileBuf,fileInfo.dwSize);       if(result==fileInfo.dwSize)       {           pImageWidth=(uint32*)(((byte*)fileBuf)+WIDTH_OFFSET);           pImageHeight=(uint32*)(((byte*)fileBuf)+HEIGHT_OFFSET);           imageWidth=*pImageWidth;           imageHeight=*pImageHeight;       }   }}定义Well-Aligned数据结构虽然我们一般不能控制定制的数据格式,比如上面例子中的BMP文件头,但是,当我们定义自己的数据结构我们可以将数据设计成Well-Aligned方式.以下例子演示这种方式:Code:#ifdef__ARMCC_VERSIONtypedefPACKEDstruct{   shorta;  //offsetof(a)=0   int  b;  //offsetof(b)=2   ?misalignmentproblem!   shortc;  //offsetof(c)=6}BAD_STRUCT;typedefstruct{   int  b;  //offsetof(b)=0   ?noproblem!   shorta;  //offsetof(a)=4   shortc;  //offsetof(c)=6}GOOD_STRUCT;简单的重新定义结构提成员的顺序,我们可以解决一些对齐问题.同时注意如果BAD_STRUCT没有定义为PACKED,编译器一般将会插入padding以使每个成员是Well-Aligned的.然而,这通常是不可取的,因为它浪费内存,而且几乎总是可以通过按顺序声明减少大小而避免。arm中的字节对齐问题(2011-04-0211:03:56)转载▼ 标签:杂谈 昨天调程序,发现一个不得解的问题,传过来的地址明明是正确的,可是一读却是一条非法指令。不写操作。很长时间不能解决,师兄过来看了一下,才现是字节对齐的问题。唉,终于碰到对齐问题了,那就好好解决一下吧。先说下我遇到的问题typedefsturct{chara;charb;charc[255];} FS;FSfs;....disk_read(...,fs->c,...)....voiddisk_read(...,int*p,...){....*p++=0x01010101; //error:....}到了这一步,也就可以看一下问题发生的原因了,在结构体中,对将c做到字节对齐,这样和disk_read中的P的四字节对齐是不同的,这样写的时候由于不是四字节对齐,就会出错下面转两篇字节对齐的文章。戒之戒之=====================================================有了上面的基础后,在一些数据结构中就要消除这些字节带来的影响,特别是在文件访问的过程中,在各个平台上都会遇到。文件为了保持最小,利用空间的原则,会按照字节来存储的,但是我们在内存中定义的结构会按最优原则使效率最大,这样会保持边界对齐。那么如何消除影响呢, 先看在 vc中如保操作 #pragmapack(push,1)    ==#paragmapack(push) #paragmapack(1)structT{    inta;    charb;}#paragmapack(pop) 再来看看ads在arm平台是如何操作的 __packedstructT{  inta;  charb;} 最后来看看gcc下面的操作__attribute__((__packed__))structT{   inta;   charb;}  最后,让我们来看看怎么定义一个结构才是移植性够好的结构 PACK_STRUCT_BEGINstructip_hdr{  PACK_STRUCT_FIELD(u16_t_id);  PACK_STRUCT_FIELD(structip_addrsrc);}PACK_STRUCT_STRUCT;PACK_STRUCT_END 通过引个宏来改变这些相应的结构#ifdef__GNU_C__#definePACK_STRUCT_FIELD(x) x#definePACK_STRUCT_STRUCT __attribute__((__packed__))#definePACK_STRUCT_BEGIN#definePACK_STRUCT_END#elif__ADS__#definePACK_STRUCT_FIELD(x)__packedx#definePACK_STRUCT_STRUCT#definePACK_STRUCT_BEGIN __packed#definePACK_STRUCT_END#elif__VC__#definePACK_STRUCT_FIELD(x)x#definePACK_STRUCT_STRUCT #definePACK_STRUCT_BEGIN #pargmapack(push,1) (问题代码) #definePACK_STRUCT_END  #pargmapack(pop)   (问题代码)#else#definePACK_STRUCT_FIELD(x)#definePACK_STRUCT_STRUCT#definePACK_STRUCT_BEGIN#definePACK_STRUCT_END#endif好啦,到此为止一切都结束了,这就是相关的字节对齐的一些操作,原来我只以为只有最后这一种情况才是呢,最上面的那种情况是后来调试才遇到的,至于以后,可能还会有,再做补充了 ps:上面有两行问题代码,自己用的时候才发现,宏定义中出现"#",几乎不可能,所以放弃这样的想法宏定义中的#可以把一个数字变成字串##则代表字符串连接。所以想要实现就要麻烦一些了,最后我是这样实现的,不好看但管用。#ifdefined_ADS___packed#elifdefined_GCC___attribute__((__packed__))#elifdefined_VC_#pargmapack(push,1)#endifstructa{...};#ifdef_VC_#pargmapack(pop)#endif=============================================================================【转】ARM平台的对齐问题(有关_packed)  2011-11-2223:08:26|  分类:嵌入式&ARM|  标签:|字号大中小 订阅前言ARM流行已久,做嵌入式开发的不知道ARM不大可能。鉴于其所具备的较低功耗下的较高性能,也就成了大多数嵌入式设备的首选了。不过对于刚上手的人来说,有可能会遇到一些稀奇古怪的问题。毕竟大部分人都习惯了IA-32下的程序设计,虽然两者都是32位的处理器,但是体系架构完全不同,于是也导致了一些隐含的问题。这里想描述一下一个有点蛊惑的问题,即在ARM上访问非对齐地址内容,会出现所谓“不可预料”结果的问题。ARM内存访问的对齐问题按照ARM文档上的描述,其访问规则如下:1.一次访问4字节内容,该内容的起始地址必须是4字节对齐的位置上;2.一次访问2字节内容,该内容的起始地址必须是2字节对齐的位置上;(单字节的没有这个问题,就不用考虑啦。)好,既然规则如此,那应该遵守。不过么,不安分的人往往喜欢破坏规则,喜欢看看不遵守规则会有什么结果;另外么,即便遵规蹈距的人,有时也难免考虑不周,犯个错也是正常现象。好,那么让我们来看看犯错的结果吧。例如下面的代码:char       buff[8]={0x12,0x34,0x56,0x78,0x9a,0xab,0xbc,0xcd};int         v32,*p32;short      v16,*p16;p32=(int*)&(buff[1]);      //unalignmentp16=(short*)&(buff[1]);     //unalignmentv32=*p32;     //what’stheresult?v16=*p16;     //what’stheresult?如果上面这段代码在IA-32上运行,那么结果应该如下:v32=0x9a785634v16=0x5634即便非对齐地址上访问,IA-32也就是牺牲一点性能,但是结果保证是正确的。恩,这也是我们所期望的……可是……换到ARM上呢?我们来看看在ADS1.2编译后,执行的结果如下:v32=0x12785634v16=0x1234这个结果有点奇怪了吧。照理说指向0x34,那么如果是Big-Endian的话,v32应该是0x3456789a,如果是Little-Endian的话,就是前面IA-32的结果。可现在的结果呢?两者都不是,莫名地把更低地址的0x12给凑进来了……而如果看看编译生成的汇编code的话,这两个赋值很简单,分别用了ldr和ldrsh指令,指令没有问题,分别用于读取32位和16位数据,都是最基本的指令。嗯,嗯,这就是我们所要描述的访问非对齐地址的问题了。问题的缘由(个人猜测,非官方资料……)个人感觉呢,这是ARM体系架构实现的问题,或者说这本来就是ByDesign的。这样做简化了处理器的实现,IA-32实现的时候肯定会对读取地址是否对齐进行判断,然后转换为相应的操作,而ARM呢?没有做这个事情,默认认为大家都按照规矩办事,你要是胆敢破坏,俺就给你好看~~~那有没有办法解决呢?这个问题其实ARM自己也知道,所以呢,它在编译器里面,已经添加了部分支持。不过有人会问,那上面那个情况呢?为什么结果还是不对呢?好像没有添加什么支持嘛……嗯,其实ARM是做了一定的努力的,只是这个情况它没办法解决……它做的事情就是:在编译器能够的得知的情况下,尽量保证访问内容的正确。这句话有点笼统,那么把具体情况一个个来看看吧。编译器的努力(1)——所有局部/全局/静态等变量都放在4字节对齐的地址上其实这个努力很常见,由于在32位平台上,一次访问4字节是效率最高的,所以大多数32平台的编译器都如此处理,ARM的ADS也不例外。编译器的努力(2)——填充、填充、再填充这个事情么,其实也是常见的。各类编译器上,对于某些结构定义中会产生不对齐的情况,自动填充,以提高访问效率(例如IA-32上访问非对齐的,会加1个周期的)。而ARM的编译器也一样操作,不过感觉这里不单单是为了提高效率,也能够顺带解决这个不对齐的问题。编译器的努力(3)——产生特殊代码嗯,这个就是关键了,也是ARM编译器的与众不同之处。先来看一段代码:__packedtypedefstruct_test{       chara;       shortc;       intd;}test;char       buff[8]={0x12,0x34,0x56,0x78,0x9a,0xab,0xbc,0xcd};test       *p=(test*)buff;v32=     p->d;      //这里的v32借用上面的定义;貌似多了个限定为__packed的struct,以此来造成不对齐的状况,看不出多大区别嘛。可是运行一下的话,就会发现这里的结果是正确的。我们来看看ADS生成的汇编代码吧。       v32=q->d;[0xe2890003]      add         r0,r9,#3[0xeb000088]      bl          __rt_uread4[0xe1a05000]      mov         r5,r0看到这里的那条"bl          __rt_uread4"的指令了吧。对ARM指令有一定了解的都知道bl其实就是一个函数调用。所以,这里的代码其实是调用了ADS自己提供的__rt_uread4函数,该函数完成的操作就是读取四个字节。ADS提供了类似的一系列函数,针对signed/unsigned,以及4字节/2字节的读取/写入操作。估计看到这里,大家会问,如果没有__packed限定符呢?猜对了,没有__packed限定符,那么编译器会对上面的情况pending,所以这个struct里面的d所在的位置是4字节对齐的(编译期信息,而非实际运行期信息)。所以就回到类似最初的例子了。那么,还有一种情况,就是在有__packed的情况下,而struct里的字段都是符合对齐要求的,那么生成的代码会是怎么样的呢?从实际生成的代码来看,和上面的这段汇编代码,唯一的区别就是第一条指令把#3改成了#4,而后面仍旧调用__rt_uread4函数。嗯,这样结论就出来了:编译器会在使用__packed的情况下,自动对其中的4字节/2字节访问添加特殊代码,以保证其结果的正确。好了,这个关于这个问题描述得差不多了,可能的话,尽量倚赖编译器的这些功能,而对于编译器无能为力的部分,就要靠万分小心了……p.s.其实这里有很多事情可以来尽量预防此类问题,比如嵌入式项目往往喜欢自己管理内存分配,那么自己写的内存分配函数就保证返回的地址都是4字节对齐位置上的……窗体底端热门搜索:维修手册图纸matlab维修手册protel构建电子师交流的平台--在交流中进一步学习设计技巧、规划技术人生、提升自我价值!ARM内存边界对齐以及sizeof问题  默认情况下,在32位cpu里,gcc对于结构体的对齐方式是按照四个字节来对齐的。看以下结构体  typedefstructpack{  chara;  intb;  shortc;  }pack;  对于Pack结构体,默认情况下在arm/386平台下(别的平台没试过)sizeof(pack)=12,求解过程如下:  sizeof(char)=1;  下一个intb,由于是四个字节,要求b的开始地址从32的整数倍开始,故需要在a后面填充3个没用的字节,记为dump(3),sizeof(b)=4,此时相当于结构体扩充为  chara;  chardump(3);  intb;  看shortc,现在c的前面有8个字节,c是两个字节,c的开始地址是从16的整数开始,在b前面不需再加东西.此时对于结构体来说,sizeof(pack)=10,但是这不是最终结果,最后总的字节数也要能被4个字节整除,所以还需在shortc后面再加  dump(2);  故总的字节数为12.  当然以上说的只是简单的情况,下面谈谈arm,x86在gcc里关于内存边界字节对齐的区别.对于同样的结构体,在386下  #pramapack(1)  后,sizeof(pack)=142=7  而在arm下同样的操作sizeof(pack)=1421=8,即虽然b根a之间不要填充但总的长度必须要是4的整数倍.  在arm下要使结构体按指定字节对齐,可行的  1.在makefile里加-fpack-struct选项,这样的话对所有的结构按一字节对齐.  不得不说,确实有那么些质量较差的程序可能需要你部分自然对齐,部分一字节对齐,此时  2.typedefstructpack{  }__attribute__((packed))  可利用__attribute__属性  当然最后的方式,还是自己去看arm体系结构与gcc编译选项了。 ++ARM平台上编译器的地址对齐/指针优化问题?欢迎大家讨论!++ 在ARMcore的平台(StrongARM,2410,XScale)上,如果企图通过一个独立的指针对某个数据结构的内部成员域进行定位和访问时,会遇到下面的问题(注,在IA-32X86平台不会出现这样的问题)。定义一个数据结构:struct__attribute__((packed))test{charc;//1个字节shorts;//2个字节longl;//4个字节}st;packed属性,对应于gcc编译选项-fpack-struct;test的内存布局在该属性的制约下是紧凑的,也就意味着第2、3个成员s、l的起始地址将是奇地址。如果另行定义一个独立指针:unsignedlong*t;通过“t+偏移字节数”的方式来定位(访问)结构内成员域,则会在强制类型转换t指针来赋值或者读取相应成员域时,出现下面的问题:(假定该结构的起始地址为A0,则c、s、l的地址各为A0、A1、A3)1.编译器版本(armversion):GCC2.95.2/3.2/3.3.2t=(unsignedlong)&st;*((short*)(t+1))=0x0201;期望:0x01赋于A1字节,0x02赋于A2字节实际:0x01赋于A0字节,0x02赋于A1字节,与期望不吻合*((long*)(t+3))=0x08070605;期望:0x05-0x08逐次赋于A3-A6字节实际:0x05-0x08逐次赋于A0-A3字节,与期望不吻合结论:基于这些GCC版本,如果对奇地址进行强制类型转换,企图获得"字、双字"的数据类型,则强制转换后所获得的数据类型,其地址将被自动"优化"为最近的可被相应对齐模数(字型数据的对齐模数是2,双字的是4)整除的低位偶地址!2.编译器版本(armversion):GCC2.95.3/3.0t=(unsignedlong)&st;*((short*)(t+1))=0x0201;期望:0x01赋于A1字节,0x02赋于A2字节实际:0x01赋于A1字节,0x02赋于A2字节,与期望吻合*((long*)(t+3))=0x08070605;期望:0x05-0x08逐次赋于A3-A6字节实际:0x05-0x08逐次赋于A0-A3字节,与期望不吻合结论:基于这些GCC版本,如果对奇地址进行强制类型转换,企图获得"双字"的数据类型,则强制转换后所获得的数据类型,其地址将被自动"优化"为最近的可被相应对齐模数4整除的低位偶地址!但对字型数据的此类操作,却不会引起这种指针优化。附,有资料称:某些架构上访问数据时有对齐的要求,比如只能从4字节边界上读取一个4字节的数据类型。IA-32架构没有硬性要求对齐,尽管未对齐的访问降低执行效率。另外一些架构,比如MIPS、SPARC、m68k、ARM(<V3),要求对齐访问,否则向当前进程分发SIGBUS信号,主要是相对早期的RISCCPU存在此问题。那么有没有一个办法(比如gcc的编译选项,或者一个__attribute__),能制约上述的这种指针优化,使其不自动发生!从而使实际操作与期望操作相吻合?进一步地问,为何不同版本的arm-linux-gcc所编译出的执行码的在这个问题上的表现不一样!?这后面有更深层的理论背景吗?arm结构体对齐问题分类:嵌入式系统2008-10-2920:321003人阅读评论(0)收藏举报Arm结构体gcc内存边界对齐问题这段时间移植公司的linuxi386程序到Armlinux平台,本以为是件工作量很小的事情,以为只要改几个驱动程序就OK了,没想到在应用程序这一块卡了很长时间。其中最烦的事情就莫过于结构体内存边界对齐了。搞了这么久,终于终结了一些小经验。默认情况下,在32位cpu里,gcc对于结构体的对齐方式是按照四个字节来对齐的。看以下结构体typedefstructpack{chara;intb;shortc;}pack;对于Pack结构体,默认情况下在arm/386平台下(别的平台没试过)sizeof(pack)=12,求解过程如下:sizeof(char)=1;下一个intb,由于是四个字节,要求b的开始地址从32的整数倍开始,故需要在a后面填充3个没用的字节,记为dump(3),sizeof(b)=4,此时相当于结构体扩充为chara;chardump(3);intb;看shortc,现在c的前面有8个字节,c是两个字节,c的开始地址是从16的整数开始,在b前面不需再加东西.此时对于结构体来说,sizeof(pack)=10,但是这不是最终结果,最后总的字节数也要能被4个字节整除,所以还需在shortc后面再加dump(2);故总的字节数为12.当然以上说的只是简单的情况,下面谈谈Arm,x86在gcc里关于内存边界字节对齐的区别.对于同样的结构体,在386下#pramapack(1)后,sizeof(pack)=142=7而在arm下同样的操作sizeof(pack)=1421=8,即虽然b根a之间不要填充但总的长度必须要是4的整数倍.在ARM下要使结构体按指定字节对齐,可行的方法1.在makefile里加-fpack-struct选项,这样的话对所有的结构按一字节对齐.不得不说,确实有那么些质量较差的程序可能需要你部分自然对齐,部分一字节对齐,此时2.typedefstructpack{}__attribute__((packed))可利用__attribute__属性当然最后的方式,还是自己去看ARM体系结构与gcc编译选项了。------------------------------------------------------------------------------------------------------------浅谈结构体对齐问题#include<stdio.h>intmain(){structms{doublex;chara;inty;};//}__attribute__((packed));printf("%d/n",sizeof(structms));return0;}linux上运行,结果为16;如果采用注释的那一行,则结果为13原文::http://dev.csdn.net/article/48/48195.shtm什么是内存对齐考虑下面的结构:structfoo{charc1;shorts;charc2;inti;};假设这个结构的成员在内存中是紧凑排列的,假设c1的地址是0,那么s的地址就应该是1,c2的地址就是3,i的地址就是4。也就是c100000000,s00000001,c200000003,i00000004。可是,我们在Visualc/c++6中写一个简单的程序:structfooa;printf("c1%p,s%p,c2%p,i%p/n",(unsignedint)(void*)&a.c1-(unsignedint)(void*)&a,(unsignedint)(void*)&a.s-(unsignedint)(void*)&a,(unsignedint)(void*)&a.c2-(unsignedint)(void*)&a,(unsignedint)(void*)&a.i-(unsignedint)(void*)&a);运行,输出:c100000000,s00000002,c200000004,i00000008。为什么会这样?这就是内存对齐而导致的问题。为什么会有内存对齐以下内容节选自《IntelArchitecture32Manual》。字,双字,和四字在自然边界上不需要在内存中对齐。(对字,双字,和四字来说,自然边界分别是偶数地址,可以被4整除的地址,和可以被8整除的地址。)无论如何,为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。一个字或双字操作数跨越了4字节边界,或者一个四字操作数跨越了8字节边界,被认为是未对齐的,从而需要两次总线周期来访问内存。一个字起始地址是奇数但却没有跨越字边界被认为是对齐的,能够在一个总线周期中被访问。某些操作双四字的指令需要内存操作数在自然边界上对齐。如果操作数没有对齐,这些指令将会产生一个通用保护异常(#GP)。双四字的自然边界是能够被16整除的地址。其他的操作双四字的指令允许未对齐的访问(不会产生通用保护异常),然而,需要额外的内存总线周期来访问内存中未对齐的数据。编译器对内存对齐的处理缺省情况下,c/c++编译器默认将结构、栈中的成员数据进行内存对齐。因此,上面的程序输出就变成了:c100000000,s00000002,c200000004,i00000008。编译器将未对齐的成员向后移,将每一个都成员对齐到自然边界上,从而也导致了整个结构的尺寸变大。尽管会牺牲一点空间(成员之间有空洞),但提高了性能。也正是这个原因,我们不可以断言sizeof(foo)==8。在这个例子中,sizeof(foo)==12。如何避免内存对齐的影响那么,能不能既达到提高性能的目的,又能节约一点空间呢?有一点小技巧可以使用。比如我们可以将上面的结构改成:structbar{charc1;charc2;shorts;inti;};这样一来,每个成员都对齐在其自然边界上,从而避免了编译器自动对齐。在这个例子中,sizeof(bar)==8。这个技巧有一个重要的作用,尤其是这个结构作为API的一部分提供给第三方开发使用的时候。第三方开发者可能将编译器的默认对齐选项改变,从而造成这个结构在你的发行的DLL中使用某种对齐方式,而在第三方开发者哪里却使用另外一种对齐方式。这将会导致重大问题。比如,foo结构,我们的DLL使用默认对齐选项,对齐为c100000000,s00000002,c200000004,i00000008,同时sizeof(foo)==12。而第三方将对齐选项关闭,导致c100000000,s00000001,c200000003,i00000004,同时sizeof(foo)==8。如何使用c/c++中的对齐选项vc6中的编译选项有/Zp[1|2|4|8|16],/Zp1表示以1字节边界对齐,相应的,/Zpn表示以n字节边界对齐。n字节边界对齐的意思是说,一个成员的地址必须安排在成员的尺寸的整数倍地址上或者是n的整数倍地址上,取它们中的最小值。也就是:min(sizeof(member),n)实际上,1字节边界对齐也就表示了结构成员之间没有空洞。/Zpn选项是应用于整个工程的,影响所有的参与编译的结构。要使用这个选项,可以在vc6中打开工程属性页,c/c++页,选择CodeGeneration分类,在Structmemberalignment可以选择。要专门针对某些结构定义使用对齐选项,可以使用#pragmapack编译指令。指令语法如下:#pragmapack([show]|[push|pop][,identifier],n)意义和/Zpn选项相同。比如:#pragmapack(1)structfoo_pack{charc1;shorts;charc2;inti;};#pragmapack()栈内存对齐我们可以观察到,在vc6中栈的对齐方式不受结构成员对齐选项的影响。(本来就是两码事)。它总是保持对齐,而且对齐在4字节边界上。验证代码#include<stdio.h>structfoo{charc1;shorts;charc2;inti;};structbar{charc1;charc2;shorts;inti;};#pragmapack(1)structfoo_pack{charc1;shorts;charc2;inti;};#pragmapack()intmain(intargc,char*argv[]){charc1;shorts;charc2;inti;structfooa;structbarb;structfoo_packp;printf("stackc1%p,s%p,c2%p,i%p/n",(unsignedint)(void*)&c1-(unsignedint)(void*)&i,(unsignedint)(void*)&s-(unsignedint)(void*)&i,(unsignedint)(void*)&c2-(unsignedint)(void*)&i,(unsignedint)(void*)&i-(unsignedint)(void*)&i);printf("structfooc1%p,s%p,c2%p,i%p/n",(unsignedint)(void*)&a.c1-(unsignedint)(void*)&a,(unsignedint)(void*)&a.s-(unsignedint)(void*)&a,(unsignedint)(void*)&a.c2-(unsignedint)(void*)&a,(unsignedint)(void*)&a.i-(unsignedint)(void*)&a);printf("structbarc1%p,c2%p,s%p,i%p/n",(unsignedint)(void*)&b.c1-(unsignedint)(void*)&b,(unsignedint)(void*)&b.c2-(unsignedint)(void*)&b,(unsignedint)(void*)&b.s-(unsignedint)(void*)&b,(unsignedint)(void*)&b.i-(unsignedint)(void*)&b);printf(&
/
本文档为【字节对齐】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。 本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。 网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。

历史搜索

    清空历史搜索