设置内存单元格对齐方式式后程序是否具有移植性

         在我们的程序中,数据结构还有变量等等都需要占有内存,在很多系统中,它都要求内存分配的时候要对齐,这样做的好处就是可以提高访问内存的速度。 我们还是先来看一段简单的程序:                                程序一
&#160;1&#160;#include&#160;&iostream&&#160;2&#160;using&#160;namespace&#160;&#160;3&#160;&#160;4&#160;struct&#160;X1&#160;5&#160;{&#160;6&#160;&#160;&#160;int&#160;i;//<span style="COLOR: #个字节&#160;7&#160;&#160;&#160;char&#160;c1;//<span style="COLOR: #个字节&#160;8&#160;&#160;&#160;char&#160;c2;//<span style="COLOR: #个字节&#160;9&#160;};<span style="COLOR: #&#160;<span style="COLOR: #&#160;struct&#160;X2<span style="COLOR: #&#160;{<span style="COLOR: #&#160;&#160;&#160;char&#160;c1;//<span style="COLOR: #个字节<span style="COLOR: #&#160;&#160;&#160;int&#160;i;//<span style="COLOR: #个字节<span style="COLOR: #&#160;&#160;&#160;char&#160;c2;//<span style="COLOR: #个字节<span style="COLOR: #&#160;};<span style="COLOR: #&#160;<span style="COLOR: #&#160;struct&#160;X3<span style="COLOR: #&#160;{<span style="COLOR: #&#160;&#160;&#160;char&#160;c1;//<span style="COLOR: #个字节<span style="COLOR: #&#160;&#160;&#160;char&#160;c2;//<span style="COLOR: #个字节<span style="COLOR: #&#160;&#160;&#160;int&#160;i;//<span style="COLOR: #个字节<span style="COLOR: #&#160;};<span style="COLOR: #&#160;int&#160;main()<span style="COLOR: #&#160;{&#160;&#160;&#160;<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;cout&&"long&#160;"&&sizeof(long)&&"\n";<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;cout&&"float&#160;"&&sizeof(float)&&"\n";<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;cout&&"int&#160;"&&sizeof(int)&&"\n";<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;cout&&"char&#160;"&&sizeof(char)&&"\n";<span style="COLOR: #&#160;<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;X1&#160;x1;<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;X2&#160;x2;<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;X3&#160;x3;<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;cout&&"x1&#160;的大小&#160;"&&sizeof(x1)&&"\n";<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;cout&&"x2&#160;的大小&#160;"&&sizeof(x2)&&"\n";<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;cout&&"x3&#160;的大小&#160;"&&sizeof(x3)&&"\n";<span style="COLOR: #&#160;&#160;&#160;&#160;&#160;return&#160;<span style="COLOR: #;<span style="COLOR: #&#160;}
&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;这段程序的功能很简单,就是定义了三个结构X1,X2,X3,这三个结构的主要区别就是内存数据摆放的顺序,其他都是一样的,另外程序输入了几种基本类型所占用的字节数,以及我们这里的三个结构所占用的字节数。这段程序的运行结果为:
<span style="COLOR: #&#160;long&#160;<span style="COLOR: #<span style="COLOR: #&#160;float&#160;<span style="COLOR: #<span style="COLOR: #&#160;int&#160;<span style="COLOR: #<span style="COLOR: #&#160;char&#160;<span style="COLOR: #<span style="COLOR: #&#160;x1&#160;的大小&#160;<span style="COLOR: #<span style="COLOR: #&#160;x2&#160;的大小&#160;<span style="COLOR: #<span style="COLOR: #&#160;x3&#160;的大小&#160;<span style="COLOR: #
&#160;&#160;&#160;&#160; 结果的前面四行没有什么问题,但是我们在最后三行就可以看到三个结构占用的空间大小不一样,造成这个原因就是内部数据的摆放顺序,怎么会这样呢?&#160;&#160;&#160; 下面就是我们需要讲的内存对齐了。&#160;&#160;&#160; 内存是一个连续的块,我们可以用下面的图来表示,&#160; 它是以4个字节对一个对齐单位的:&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160;&#160; 图一
&#160;&#160; 让我们看看三个结构在内存中的布局:&#160;&#160; 首先是 X1,如下图所示
&#160;&#160;&#160;&#160;X1 中第一个是 Int类型,它占有4字节,所以前面4格就是满了,然后第二个是char类型,这中类型只占一个字节,所以它占有了第二个4字节组块中的第一格,第三个也是char类型,所以它也占用一个字节,它就排在了第二个组块的第二格,因为它们加在一起大小也不超过一个块,所以他们三个变量在内存中的结构就是这样的,因为有内存分块对齐,所以最后出来的结果是8,而不是6,因为后面两个格子其实也算是被用了。&#160;&#160;&#160; 再次看看X2,如图所示
&#160;&#160;&#160;&#160;X2中第一个类型是Char类型,它占用一个字节,所以它首先排在第一组块的第一个格子里面,第二个是Int类型,它占用4个字节,第一组块已经用掉一格,还剩3格,肯定是无法放下第二Int类型的,因为要考虑到对齐,所以不得不把它放到第二个组块,第三个类型是Char类型,跟第一个类似。所因为有内存分块对齐,我们的内存就不是8个格子了,而是12个了。再看看X3,如下图所示:
&#160;&#160;&#160;关于X3的说明其实跟X1是类似的,只不过它把两个1个字节的放到了前面,相信看了前面两种情况的说明这里也是很容易理解的。&#160;&#160; 唉,写到这手都累了,关键是要画图,希望通过此文能让你理解内存对齐的基本概念,如果有问题,请留言。此文完
辛苦了!&&&&
&re: 内存对齐
太好了&&&&
&re: 内存对齐
辛苦了,&&&&
&re: C++中的内存对齐
概念都懂,要谈一些编程中实际遇到的问题,怎么解决.因为结构的指针是一个很方便的东西,大家经常用他来对一个数据块(连续字节构成的,没有空洞的)进行格式化(map),以对各个变量进行访问.&&&&
&re: C++中的内存对齐
好, 楼主辛苦了!&&&&
&re: C++中的内存对齐
辛苦了学习&&&&
&re: C++中的内存对齐
简单问题,看看VC或BCB的结果声明的最前面。使用以下语句将结构声明包起来就行了。#pragma
pack(push,1)#pragma
&re: C++中的内存对齐[未登录]
各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节自动填充。同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
这样不是已经很清楚了吗,不用写这么长吧。&&&&
&re: C++中的内存对齐
&re: C++中的内存对齐
非常清晰,明白了本来不明白的东西。也感谢TheAnswer的总结&&&&
&re: C++中的内存对齐
谢谢!!!&&&&
&re: C++中的内存对齐[未登录]
看了之后,很有启发。谢谢!!!&&&&
&re: C++中的内存对齐
谢谢,这样画出来比较清晰,谢谢楼主&&&&
&re: C++中的内存对齐
真是太谢谢了
光看别人说什么好多好多
还不如这三个画呢
下面再理解那几个命令关键字
就没什么问题了 太谢谢了
&re: C++中的内存对齐
图画得很好,看懂了。谢谢!&&&&
&re: C++中的内存对齐
搂主所说好像不够全面 ,按照4字节对齐
struct
同意 TheAnswer
各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节自动填充。同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
&re: C++中的内存对齐
感谢,终于明白原因了。&&&&
&re: C++中的内存对齐
VC给的结果是8.@colorrain&&&&
&re: C++中的内存对齐
@colorrain&&&&
&re: C++中的内存对齐
不好意思,忘记了程序加了一句#pragma pack(1)去掉后默认的就是12了。。还是theAnswer说的对。@colorrain&&&&
&re: C++中的内存对齐
楼主辛苦了,但是讲解的不够清晰。个人觉得这篇文章更加详细&&&&
&re: C++中的内存对齐
楼主辛苦,多谢!&&&&
&re: C++中的内存对齐
还是感觉不够全:struct A{}这种情况,
16;win下是以最长字节数补齐的,
下则是12,Unix下则是最长不超过4是以最长字节为节点补齐,超过4 时 就以4的倍数为节点补齐;&&&&
&re: C++中的内存对齐
结构体中,三个short为什么是6呢,按照你的说法应该是8啊&&&&
&re: C++中的内存对齐
配的图清晰易懂,赞一个&&&&
&re: C++中的内存对齐[未登录]
short是按照2对齐的6是对的@cai&&&&
&re: C++中的内存对齐[未登录]
按2对齐啊@cai&&&&
&re: C++中的内存对齐
楼主辛苦了,非常感谢&&&&
&re: C++中的内存对齐
@牛在蓝天补充的很好,谢谢~&&&&
&re: C++中的内存对齐[未登录]
解释的十分精辟!&&&&
&re: C++中的内存对齐[未登录]
看过好几个内存对齐了,你的最直观!!!&&&&一、内存对齐
许多计算机系统对基本类型数据在内存中存放的位置有限制,它们会要求这些数据的首地址的&#20540;是某个数k(通常它为4或8)的倍数,这就是所谓的内存对齐,而这个k则被称为该数据类型的对齐模数(alignment modulus)。当一种类型S的对齐模数与另一种类型T的对齐模数的比&#20540;是大于1的整数,我们就称类型S的对齐要求比T强(严&#26684;),而称T比S弱(宽松)。这种强制的要求一来简化了处理器与内存之间传输系统的设计,二来可以提升读取数据的速度。比如这么一种处理器,它每次读写内存的时候都从某个8倍数的地址开始,一次读出或写入8个字节的数据,假如软件能保证double类型的数据都从8倍数地址开始,那么读或写一个double类型数据就只需要一次内存操作。否则,我们就可能需要两次内存操作才能完成这个动作,因为数据或许恰好横跨在两个符合对齐要求的8字节内存块上。某些处理器在数据不满足对齐要求的情况下可能会出错,但是Intel的IA32架构的处理器则不管数据是否对齐都能正确工作。不过Intel奉劝大家,如果想提升性能,那么所有的程序数据都应该尽可能地对齐。
ANSI C标准中并没有规定,相邻声明的变量在内存中一定要相邻。为了程序的高效性,内存对齐问题由编译器自行灵活处理,这样导致相邻的变量之间可能会有一些填充字节。对于基本数据类型(int char),他们占用的内存空间在一个确定硬件系统下有个确定的&#20540;,所以,接下来我们只是考虑结构体成员内存分配情况。
Win32平台下的微软C编译器(cl.exe for 80×86)的对齐策略:
1) 结构体变量的首地址能够被其最宽基本类型成员的大小所整除;
备注:编译器在给结构体开辟空间时,首先找到结构体中最宽的基本数据类型,然后寻找内存地址能被该基本数据类型所整除的位置,作为结构体的首地址。将这个最宽的基本数据类型的大小作为上面介绍的对齐模数。
2) 结构体每个成员相对于结构体首地址的偏移量(offset)都是成员大小的整数倍,如有需要编译器会在成员之间加上填充字节(internal adding);
备注:为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的首地址相对于结构体首地址的偏移是否是本成员的整数倍,若是,则存放本成员,反之,则在本成员和上一个成员之间填充一定的字节,以达到整数倍的要求,也就是将预开辟空间的首地址后移几个字节。
3) 结构体的总大小为结构体最宽基本类型成员大小的整数倍,如有需要,编译器会在最末一个成员之后加上填充字节(trailing padding)。
备注:结构体总大小是包括填充字节,最后一个成员满足上面两条以外,还必须满足第三条,否则就必须在最后填充几个字节以达到本条要求。
根据以上准则,在windows下,使用VC编译器,sizeof(T)的大小为8个字节。
而在GNU GCC编译器中,遵循的准则有些区别,对齐模数不是像上面所述的那样,根据最宽的基本数据类型来定。
在GCC中,对齐模数的准则是:对齐模数最大只能是4,也就是说,即使结构体中有double类型,对齐模数还是4,所以对齐模数只能是1,2,4。而且在上述的三条中,第2条里,offset必须是成员大小的整数倍,如果这个成员大小小于等于4则按照上述准则进行,但是如果大于4了,则结构体每个成员相对于结构体首地址的偏移量(offset)只能按照是4的整数倍来进行判断是否添加填充。
看如下例子:
double d ;
那么在GCC下,sizeof(T)应该等于12个字节。
如果结构体中含有位域(bit-field),那么VC中准则又要有所更改:
1) 如果相邻位域字段的类型相同,且其位宽之和小于类型的sizeof大小,则后面的字段将紧邻前一个字段存储,直到不能容纳为止;
2) 如果相邻位域字段的类型相同,但其位宽之和大于类型的sizeof大小,则后面的字段将从新的存储单元开始,其偏移量为其类型大小的整数倍;
3) 如果相邻的位域字段的类型不同,则各编译器的具体实现有差异,VC6采取不压缩方式(不同位域字段存放在不同的位域类型字节中),Dev-C&#43;&#43;和GCC都采取压缩方式;
备注:当两字段类型不一样的时候,对于不压缩方式,例如:
依然要满足不含位域结构体内存对齐准则第2条,i成员相对于结构体首地址的偏移应该是4的整数倍,所以c成员后要填充3个字节,然后再开辟4个字节的空间作为int型,其中4位用来存放i,所以上面结构体在VC中所占空间为8个字节;而对于采用压缩方式的编译器来说,遵循不含位域结构体内存对齐准则第2条,不同的是,如果填充的3个字节能容纳后面成员的位,则压缩到填充字节中,不能容纳,则要单独开辟空间,所以上面结构体N在GCC或者Dev-C&#43;&#43;中所占空间应该是4个字节。
4) 如果位域字段之间穿插着非位域字段,则不进行压缩;
结构体5) 整个结构体的总大小为最宽基本类型成员大小的整数倍。
&& char c:2;
&& double i;
&& int c2:4;
在GCC下占据的空间为16字节,在VC下占据的空间应该是24个字节。
对齐模数的选择只能是根据基本数据类型,所以对于结构体中嵌套结构体,只能考虑其拆分的基本数据类型。而对于对齐准则中的第2条,确是要将整个结构体看成是一个成员,成员大小按照该结构体根据对齐准则判断所得的大小。
类对象在内存中存放的方式和结构体类&#20284;,这里就不再说明。需要指出的是,类对象的大小只是包括类中非静态成员变量所占的空间,如果有虚函数,那么再另外增加一个指针所占的空间即可。
1.&&&&&&&&& 内存对齐与编译器设置有关,首先要搞清编译器这个默认&#20540;是多少
2.&&&&&&&&& 如果不想编译器默认的话,可以通过#pragma pack(n)来指定按照n对齐
3.&&&&&&&&& 每个结构体变量对齐,如果对齐参数n(编译器默认或者通过pragma指定)大于该变量所占字节数(m),那么就按照m对齐,内存偏移后的地址是m的倍数,否则是按照n对齐,内存偏移后的地址是n的倍数。也就是最小化长度规则
4.&&&&&&&&& 结构体总大小:
对齐后的长度必须是成员中最大的对齐参数的整数倍。最大对齐参数是从第三步得到的。
5.&&&&&&&&& 补充:如果结构体A中还要结构体B,那么B的对齐方式是选它里面最长的成员的对齐方式
有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:
struct 位域结构名
{ 位域列表 };
其中位域列表的形式为: 类型说明符 位域名:位域长度
位域变量的说明与结构变量说明的方式相同。 可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:
说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:
1. 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*从下一单元开始存放*/
unsigned c:4
在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。
2. 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。
3. 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:
int :2 /*该2位不能使用*/
从以上分析可以看出,位域在本质上就是一种结构类型, 不过其成员是按二进位分配的。
三。位域的使用
位域的使用和结构成员的使用相同,其一般形式为: 位域变量名·位域名 位域允许用各种&#26684;式输出。
unsigned a:1;
unsigned b:3;
unsigned c:4;
printf(&%d,%d,%d/n&,bit.a,bit.b,bit.c);
pbit-&a=0;
pbit-&b&=3;
pbit-&c|=1;
printf(&%d,%d,%d/n&,pbit-&a,pbit-&b,pbit-&c);
上例程序中定义了位域结构bs,三个位域为a,b,c。说明了bs类型的变量bit和指向bs类型的指针变量pbit。这表示位域也是可以使用指针的。
程序的9、10、11三行分别给三个位域赋&#20540;。( 应注意赋&#20540;不能超过该位域的允许范围)程序第12行以整型量&#26684;式输出三个域的内容。第13行把位域变量bit的地址送给指针变量pbit。第14行用指针方式给位域a重新赋&#20540;,赋为0。第15行使用了复合的位运算符&&=&, 该行相当于: pbit-&b=pbit-&b&3位域b中原有&#20540;为7,与3作按位与运算的结果为3(111&011=011,十进制&#20540;为3)。同样,程序第16行中使用了复合位运算&|=&, 相当于: pbit-&c=pbit-&c|1其结果为15。程序第17行用指针方式输出了这三个域的&#20540;。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:3786次
排名:千里之外
原创:13篇1017人阅读
计算机体系结构
1, 内存对齐
为什么要讨论内存对齐问题呢?因为最近在写BMP头文件的时候出现了些问题,后来发现是结构体中内存对齐所致的。
当时情况如下:
16 typedef struct
18&&&& uint16_t& &
19&&&& uint32_t& file_&&
20&&&& uint16_t& reserved1;&&
21&&&& uint16_t& reserved2;&
22&&&& uint32_t& bmp_&
23 }BITMAPFILEHEADER;
// 对这个结构体赋值
277&&&& BITMAPFILEHEADER&&
278&&&& memset(&bfh, 0, sizeof(BITMAPFILEHEADER));
279&&&& bfh.identifier = 0x4d42; // 'B','M'
280&&&& bfh.file_size& = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + rgb24_
281&&&& bfh.reserved1& = 0;
282&&& &bfh.reserved2& = 0;
283&&&& bfh.bmp_offset = bfh.file_size - rgb24_
// 把这个结构体写入到头文件中
304&&&& if (fwrite(&bfh, 14, 1, fp) & 0){
305&&&&&&&& perror("write_rgb24_bmp:fwrite BITMAPFILEHEADER:");
306&&&&&&&& return -1;
运行程序后,用十六进制格式查看文件,竟然多了个0000,一时十分不解。
d42 <span style="color: # 00 00
将内存中的数据写入文件时,文件中的数据排列与内存中的是一样的,所以肯定0000是分配结构体变量空间时所分配的一个字节。故对结构体变量成员内存空间的分配做了如下探讨。
先看一下程序:
1 #include
& 2 struct t1_stru
& 5&&&& int&
&10 int main()
&12&&&& t1.ch = 0x12;&& // 这三句赋值语句可以不用理会
&13&&&& t1.in = 0x3456789A;
&14&&& &t1.sh = 0xBCDE;
&15&&&& printf("sizeof(t1.ch)=%d/n", sizeof(t1.ch));
&16&&&& printf("sizeof(t1.in)=%d/n", sizeof(t1.in));
&17&&&& printf("sizeof(t1.sh)=%d/n", sizeof(t1.sh));
&18&&&& printf("sizeof(t1)=%d/n", sizeof(t1));
&19&&&& return 0;
输出结果:
sizeof(t1.ch)=1
sizeof(t1.in)=4
sizeof(t1.sh)=2
sizeof(t1)=12
为什么会出现这样的结果呢?其实是编译器其对结构体成员进行内存对齐的所导致的。
在默认情况下,C/C++的编译器会将结构体,栈中的成员进行内存对齐。
什么是内存对齐呢?即把成员安排在某一些符合某种规律的内存空间地址上,从而加快CPU对数据的读写速度。
如果想深入了解内存对齐如何加快CPU对数据的读写速度请参考:
Data alignment:Straighten up and fly right
在讲述内存对齐规则之前,首先说明一下#pragma pack (n)语句
#pragma pack (n) 这个语句用于设置结构体的内存对齐方式,具体作用下面再说。在linux gcc 下n可取的值为:1,2,4,当n大于4时按4处理。如果程序中没用显试写出这个语句,那么在linux gcc下,它会对所有结构体都采用#pragma pack (4)的内存对齐方式。需要注意的是,在不同的编译平台上默认的内存对齐方式是不同的。如在VC中,默认是以#pragma pack (8) 的方式进行对齐。
#pragama pack (n)使用方法
#pragama pack (2)
struct structname
#pragama pack ()
上面表示在#pragama pack (2) 到 #pragama pack()之间采用n为2的内存对齐方式。#pragma pack () 表示取消自定义字节对齐方式,则在#pragama pack ()以下的程序不在使用#pragma pack (2) 的对齐方式,恢复#pragma pack (4) 这种编译器默认的对齐方式。当然没有#pragma pack ()也可以,那么则表示#pragma pack (2)以下到程序尾都采用此对齐方式。
内存对齐总规则:
结构体成员的地址必须安排在成员大小的整数倍上或者是#pragma pack(n) 所指定的n的倍数上;取两者的最小值,即MIN(sizeof(mem), n),称MIN(sizeof(mem), n)为该结构体的成员对齐模数。同时该结构体的总大小必须为MIN(n, MAX(sizeof(mem1), siezof(mem2)&))的整数倍;而称MIN(n, MAX(sizeof(mem1), siezof(mem2)&))为该结构体的对齐模数。
内存对齐细则:
下面的3条细则符合上面所说的总规则;这里的偏移指某一个数据成员的首地址到该结构体头的地址偏移。
(1) 对结构体的成员,第一个数据位于偏移为0的位置,以后每个数据成员的偏移量必须是成员对齐模数的倍数。
(2) 为结构体的一个成员开辟空间之前,编译器首先检查预开辟空间的偏移是否为成员对齐模数的整数倍,若是,则存放本成员,反之,则在本成员与上一成员之前填充一定的字节,从而达到整数倍的要求。
(3) 在数据成员完成自身的对齐后,结构体本身也要进行对齐。意思是该结构体的大小必须是结构体的对齐模数的整数倍。如果其大小不是,那么则在最后一个成员的后面填充字节。
首先计算出成员对齐模数与结构体的对齐模数:
ch_mod = MIN(4, sizeof(t1.ch)) = 1;
in_mod = MIN(4, sizeof(t1.in)) = 4;
sh_mod = MIN(4, sizeof(t1.sh)) = 2;
t1_mod = MIN(4, MAX(ch_mod, in_mod, sh_mod)) = 4;
然后用gdb调试上面的程序分析内存对齐的规则:
(gdb) p &t1
$1 = (struct t1_stru *) 0x80496d8& // t1结构体的首地址
(gdb) p &t1.ch&&&&&&&&&&&&&&&& &&&&&&&
$2 = 0x80496d8 ""&&&& &&&&&&&&&&&&&&&& // ch的首地址
(gdb) p &t1.in
$3 = (int *) 0x80496dc&&&&&&&&&& &&& // in的首地址
(gdb) p &t1.sh
$4 = (short int *) 0x80496e0&&&&& & // sh的首地址
根据细则1:
可以知道t1的结构体的首地址就是t1结构体第一个成员ch的首地址。
根据细则2:
当为in开辟空间时,编译器检查预开辟空间的偏移,即ch后面一个一节的地址空间d9偏移,为1,不是in_mod的整数倍,所以向后找,一直到地址dc,偏移为4,刚好为in对齐模数的1倍,故在此开辟向后开辟4字节的地址空间。在d8与dc之间的地址中填充数据。
根据细则3:
当为sh分配空间后,此时结构体的大小为4+4+2 = 10, 10并不是t1_mod的整数倍,故在sh后填充两字节的数据,从而达到结构体自身的对齐。所以结构体总大小为12。
从下图可以看出该结构体成员赋值的过程:
再看一下程序:
1 #include
& 2 #include
& 4 struct t1_stru
& 6&&&& uint8_t&
& 7&&&& uint32_
& 8&&&& uint16_
&11 struct t2_stru
&13&&&& uint8_
&14&&&& struct t1_stru t1;
&15&&&& uint16_
&18 int main()
&20&&&& t2.ch&&& = 0x12;
&21&&&& t2.t1.ch = 0x23;
&22&&&& t2.t1.in = 0x3456789A;
&23&&&& t2.t1.sh = 0xABCD;
&24&&&& t2.sh&&& = 0xACEF;
&25&&&& printf("sizeof(t2) = %d/n", sizeof(t2));
&26&&&& return 0;
输出结果为:
sizeof(t2) = 20
分析的方法跟上面的例子一样,当结构体中含有结构体时,计算其大小时,其实就是根据细则不断的嵌套的计算。
首先计算出t2成员对齐模数与t2结构体的对齐模数:
t2.ch_mod = MIN(4, sizeof(t2.ch) = 1;
t2.t1_mod = MIN(4, sizeof(t2.t1)) = MIN(4, 12) = 4;
(计算siezeof(t2.t1)则是按照上面的例子那样计算,得12)
t2.sh_mod = MIN(4, sizeof(t2.sh)) = 2;
t2_mod = MIN(4, MAX(t2.ch_mod, t2.t1_mod, t2.sh_mod)) = 4;
故sizeof(t2) = 20;
下图为t2的内存示意图:
位段是以位为单位定义结构体(或共用体)中成员所占存储空间的长度。含有位段的结构体类型成为位段结构。对于定义位段的变量类型只能为:字符型与整形。在Linux gcc 下,对不同类型的位段结构采用压缩存放的方法(下面规则的3,4点体现了不同类型的压缩存放的方法)。
位段结构的定义格式:类型 &成员名&:&占用位数&
在说明位段的一些规则前先解释一些名词的含义:
类型的存储单元:sizeof(类型)字节长度的内存空间
成员类型的位宽:sizeof(类型) * 8 bit
类型的存储单元位宽=成员类型的位宽
以下是位段的一些规则:
(1)第一个位段成员的偏移满足内存对齐的原则。 &(2) 相邻的位段结构成员的类型相同。若它们的位宽之和不大于该类型的存储单元的位宽,则后面的成员紧接在前一个成员后;若否,则为后面的成员开辟一个新的存储单元。新的存储单元的偏移满足内存对齐的原则。
(3)相邻的位段结构成员的类型不同,前一成员类型的位宽大于后一成员类型的位宽。若前一成员所在的存储单元还有空间容纳后一成员,则把后一成员紧接在前一成员后;若否,则为后一成员开辟新的存储单元。新的存储单元的偏移满足内存对齐的原则。
(4)相邻的位段结构成员的类型不同,前一成员类型的位宽小于后一成员类型的位宽。若把前一成员所在的存储单元的位宽扩展为后一成员类型的位宽的大小后,能把后一成员容纳下的,则把对前一成员的存储单元进行位宽扩展,并把后一成员紧接在前一成员后;若否,则为后一成员开辟新的存储空间,其存储空间的偏移满足内存对齐的原则。(存储单元的位宽扩展的原则:若前一成员所在的字节单元的偏移不为后一成员大小的整数倍,则先向前兼并字节单元扩展,直到向前找到偏移为后一成员大小的整数倍的字节单元,此时判断扩展的位数是否足够,如果不够则从后开辟新字节单元进行扩展)
(5)可以通过定义长度为0的位段的方式使下一位段从下一存储单元开始。 (6)可以定义无名位段。 (7)定义位段时其长度不能大于存储单元的长度。 (5)位段无地址,不能对位段进行取地址运算。 (6)位段可以以%d,%o,%x格式输出。 (7)位段若出现在表达式中,将被系统自动转换成整数。
(8)位段的最大取值范围不要超出二进制位数定的范围,否则超出部分会丢弃。
下图是编译器分配位段空间时的算法流程:
&看下面的列子:
& 1 #include
& 2 #include
& 4 struct s1_stru
& 6&&&& uint8_t& ch1:6;
& 7&&&& uint8_t& ch2:1;
& 8&&&& uint16_t sh:2;
&11 struct s2_stru
&13&&&& uint8_t& ch:7;
&14&&&& uint16_t sh:9;
&17 struct s3_stru
&19&&&& uint8_t ch:7;
&20&&&& uint16_t sh:10;
&23 struct s4_str
&25&&&& uint8_t& ch:7;
&26&&&& uint16_t sh:10;
&27&&&& uint32_t in:1;
&28&&&& uint16_t sh2:2;
&31 struct s5_stru
&33&&&& uint8_t& ch1:4;
&34&&&& uint16_t sh1:10;
&35&&&& uint32_t in1;
&36&&&& uint8_t& ch2:4;
&37&&&& uint16_t sh2:4;
&41 int main(void)
&43&&&& t1.ch1 = 0b111111;
&44&&&& t1.ch2 = 0b1;
&45&&&& t1.sh = 0b11;
&46&&&& t2.ch = 0b1111111;
&47&&&& t2.sh = 0b;
&48&&&& t3.ch = 0b1111111;
&49&&&& t3.sh = 0b;
&50&&&& t4.ch = 0b1111111;
&51&&&& t4.sh = 0b;
52&&&& t4.in = 0b1;
&53&&&& t4.sh2 = 0b11;
&54&&&& t5.ch1 = 0b1111;
&55&&&& t5.sh1 = 0b;
&56&&&& t5.in1 = 0xFFFFFFFF;
&57&&&& t5.ch2 = 0b1111;
&58&&&& t5.sh2 = 0b1111;
&59&&&& printf("sizeof(t1)=%d/n", sizeof(t1));
&60&&&& printf("sizeof(t2)=%d/n", sizeof(t2));
&61&&&& printf("sizeof(t3)=%d/n", sizeof(t3));
&62&&&& printf("sizeof(t4)=%d/n", sizeof(t4));
&63&&&& printf("sizeof(t5)=%d/n", sizeof(t5));
&64&&&& return 0;
输出结果:
sizeof(t1)=2
sizeof(t2)=2
sizeof(t3)=4
sizeof(t4)=4
sizeof(t5)=12
结构体变量t1的内存示意图:这幅图说明了两种情况,一是同位段类型的且能够存放在同一存储单元;二是不同位段类型的,但是经过位宽扩展后能够存放在同一单元的。
结构体变量t3的内存示意图:这幅图说明了不同类型的位段成员间需要开辟新空间的情况。
结构体变量t4的内存示意图:这幅图是上面两幅图情况的综合。
下面用图说明一下存储单元位宽扩展的原则
struct examp1
&&& uint8_t ch1:7;
&&& uint32_t in1:3;
ch1所在偏移为0,是in1类型的大小的整数倍,故从ch1向后开辟新空间扩展。一共开辟3个字节。
struct exampl2
&&& uint8_t ch1:7;
&&& uint8_t ch2:7;
&&& uint32_t in1:3;
ch2所在的偏移为1,不是in1类型的大小的整数倍,故先从ch2向前兼并字节单元,直到找到偏移为in1类型的大小的整数倍的地址。可知,ch1所在的空间为in1类型的大小的整数倍。故一共向前兼并了1个字节,但是还差两个字节,所以要从ch2向后再开辟两个字节的空间。
类似的有以下两幅图:
struct exampl3
&&& uint8_t ch1:7;
&&& uint8_t ch2:7;
&&& uint8_t ch3:7;
&&& uint32_t in1:3;
struct exampl4
&&& uint8_t ch1:7;
&&& uint8_t ch2:7;
&&& uint8_t ch3:7;
&&& uint8_t ch4:7;
&&& uint32_t in1:3;
3,结合位段结构再谈大小端
& 1 #include
& 2 #include
& 3 struct s1_srtuct
& 5&&&& uint8_t b1:1;
& 6&&&& uint8_t b2:1;
& 7&&&& uint8_t b3:1;
& 8&&&& uint8_t b4:1;
& 9&&&& uint8_t b5:1;
&10&&&& uint8_t b6:1;
&11&&&& uint8_t b7:1;
&12&&&& uint8_t b8:1;
&16 int main(void)
&18&&&& uint8_t *p = (uint8_t *)&t1;
&19&&&& t1.b1 = 1;
&20&&&& printf("t1=%d/n", *p);
&21&&&& return 0;
在linux gcc下,结构体成员空间而是按照由地址低到地址高的顺序分配的。又因为linux是为小端模式的系统,故输出为t1=1;若程序运行在大端模式的系统上,输出则为t1=128。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:54804次
排名:千里之外
原创:26篇
转载:25篇
评论:11条
(14)(4)(5)(2)(2)(2)(2)(7)(7)(2)(4)}

我要回帖

更多关于 单元格对齐方式 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信