先上两个简单的结构体,思考:这两个结构体大小是不是一样?
附上:各数据类型的字节长度
运行以下代码,查看打印结果:
这个结果有没有在意料之中,以64位系统为例,struct1存取的字节数: double为8 、char为1、int为4、short为2,这就奇怪了,为什么读取内存不是按字节大小一个个读呢?这样就是8 + 1 + 4 + 2 = 15,这样看来不是更加省内存吗?通过查阅资料发现,处理器一般会以2字节,4字节,8字节,16字节甚至32字节为单位来存取内存。以这个思路重新计算一下,double以8字节存取,char以2字节存取,int以4字节存,short以2字节存,这样就是8 + 2 + 4 +2 = 16,并不是24,反而是struct2的大小。有没有可能计算机有一种策略,为了不在各种字节存取反复横跳,例如一会4字节存取,一会2字节存取,如果这样做会导致计算机开销巨大,是不是他就会定下一种策略,统一一个字节去存取,比如统一使用8字节存取,那么4个类型就是8 * 4 = 32,这里似乎又超过24,那么很可能的是计算机既做到了不用反复横跳的存取,又实现了内存的节省,究竟是怎么回事呢?
猜测1:结构体是不是将第一个成员变量作为首地址先存取了,然后后续的成员,再按谁最大,就以多少字节存取?那么来算一下:
double : 8 ,后面3个成员分别是 char int short , int占4个字节,所以后面以4字节读取,则通过计算得出
8 + 4 + 4 + 4 = 20 ,还是不对。
猜测2:第一个成员变量作为首地址先存取,后续成员的存取是不是和当前读取到的位置存在倍数关系,再进行一次计算
double : 8 【0 ~ 7】
char : 1 【8】 有倍数关系
int : 4 (9 , 10 , 11 ) 无倍数关系 【12】有倍数关系,从这开始读4位就是【12 13 14 15】
short : 2 【16】有倍数关系 ,那就是【16,17】
竟然是奇数!好像还是不对,难道他的总大小也会有倍数关系吗?看结果 24,确实是和里面的任意一个成员都有倍数关系,那么我是不是可以合理猜测,当内存计算出的结果和里面的成员变量没有倍数关系时,他会自动补齐后续内存以达成倍数关系。从17 到 24之间 ,也只有24和所有成员变量有倍数关系,只要总大小和里面最大的成员成倍数关系,那么与其他的都是倍数关系,这也是为什么不是20,而是24.以这种思路计算struct2
double : 8 【0 ~ 7】
int : 4 【8 9 10 11】
char : 1 【12】
short : 2 【 14 15 】
这里只读取到15,和 8 并不存在倍数关系,往后再读一位 那么就是 16
我们再写多几个结构体试试
计算struct 4:
char : 1 【0】
char : 1 【1】
short : 2 【 2 3 】
int : 4 【4 5 6 7】
最后补齐结果为 : 8 ,是4的倍数
计算struct 5
int 4 【0 1 2 3】
double 8 【8 9 10 11 12 13 14 15】
long 8 【16 17 18 19 20 21 22 23】
short 2 【24 25】
最后同样要补齐结果为: 32 , 是 8的倍数
打印验证:
继续往下研究,如果结构体里也有结构体,如下图,结构体3包含了结构体5
我们依旧按照前面的方法计算前5个成员
double 8 [ 0 ~ 7]
int 4 [8 ~ 11]
char 1 [12]
short 2 [14 15]
int 4 [16 ~ 19]
那么问题来了,我们已知struct5是32,那他是从32开始,然后直接32 + 32等于64呢?还是需要从结构体5内部开始存呢?如果从结构体5内部的话,那么接着上面计算结构体5内部:
int 4 [20 ~ 23]
double 8 [24 ~ 31]
long 8 [32 ~ 39]
short 2 [40 41]
最后补齐倍数那就是8的倍数,往后存取就是48,
打印结果:
结果真的打脸,既不是64也不是48,如果计算内部时,不是以第一个成员的倍数开始,而是以最大的成员的倍数开始, 结果又会怎样呢?接着计算:
int 4 [24 ~ 27]
double 8 [32 ~ 39]
long 8 [40 ~ 47]
short 2 [48 49]
最后补齐:56
我的天,还真的是这样 ?为了进一步验证,我把struct5换成struct4,计算出的结果是32,大家可以尝试一下
结论:
关于结构体内存对齐,我得出几点原则:
1.从第一个成员开始,首地址为0,后续成员的首地址需要和他们的大小成倍数关系。
2. 最终大小如果和结构体里最大的成员无法成倍数关系,必须从当前地址继续往后偏移,直到地址和该成员成倍数关系,那么当前的地址就是结构体最终大小。
3.如果是结构体嵌套结构体,计算大小过程并不是单一的把结构体大小加上其他成员变量,而是以结构体中内部最大的成员来找到和该成员对应的地址,然后再根据 1,2点进行运算,最终得出结果。
探索收获:
为什么要进行结构体对齐?
从文章开头举的两个结构体例子中,我们可以知道,即使结构体里面的内容相同,但是大小确不是一样。通过探索结构体对齐,我明白在设计结构体中,一个好的结构体,往往能做到存取过程中,既节省空间,又能节省时间,比如例子中结构体2就比结构体1节省了8个字节,在文章上面计算struct1过程中,我们可以在计算中知道,结构体1,既要空出多余的空间,又要为了节省存取时间去补上了多余的空间,比如17到24这部分空间就是浪费了,而结构体2就没有这方面问题,从对比发现,似乎在设计结构体时,成员较小的尽量放到一起的话,会更省空间。虽然结构体内存对齐是为了用空间换取时间,但是我们作为开发者,也是可以通过巧妙的设计去帮助结构体节省空间。
网友评论