02内存对齐之结构体在内存所占字节数求法
1 结构体在内存所占字节数求法,需按成员类型划分来求
1)简单类型(即结构体没有内嵌结构体):
先求出总内存:
成员1类型所占字节数 x (0~n);
成员2类型所占字节数 x (0~n);
成员3类型所占字节数 x (0~n);
…以此类推,以0开始是因为根据上一篇我们知道,结构体内部是以0作为开始偏移,即绝对零地址。
最后:
判断结果是否符合最大类型字节数的倍数,若是直接取,不是则补0取最近的数。
注:若是数组,最大字节数按单个算,例如int a[10];最大也是4字节,char cc[20];按1字节算。
例1
typedef struct TEACHER {
char name[4];
int age;
int id;
}Teacher;
成员1:
4 x 0 =0; // 成立,开始偏移必量为0,即存储首地址为0,共存储4个字节。
成员2:
4 x 0 =0; //不成立,因为age以0开始存储的话,则与成员name重复了,所以乘数加1。
4 x 1 = 4;//成立,即age的首地址为4,也就是偏移量为4,共存储4个字节。
成员3:
4 x 0 = 0; // 不成立,内存已被存储。
4 x 1 = 4; // 不成立,内存已被存储。
4 x 2 = 2; // 成立,共存储4个字节。
加起来12个字节,且12符合最大成员字节数4的倍数。所以该结构体共占4x3=12个字节。即内存情况如下:
na na na na
ag ag ag ag
id id id id
例2 (简洁说明)
typedef struct DATA2 {
double a;
int b;
short c;
}Data2;
8 x 0 = 0; // 存8个(内存存0-7) 下一个必须为8
4 x 2 = 8; // 存4个后,下一个必须是12之后。
2 x 6 = 12; // 存2个
上面看着是总共14个,但不符合8的倍数,所以补零后变成16个。即内存情况如下:
a a a a a a a a
b b b b c c 0 0 // cc仍会存进b之后,不会新起一行存储。
例3 超过64位8字节的成员。
typedef struct DATA3 {
char a[9]; // 实际类型就是char 为1字节
int b;
short c;
}Data3;
9 x 0 = 0; //存了9个,8开始的偏移量剩余3不足存储一个int所以会补0
4 x 3 = 12;//存4个字节
2 x 8 = 16;//存了两个
每次存了多少个,必须用图画出来。即
a a a a //每行四个是因为最大字节类型是int=4字节
a a a a
a 0 0 0 //只剩余3个字节不足以存储int
b b b b
c c 0 0 //存两个,并补两个0
由于上面已经补了0,得出的结果肯定符合4的倍数,所以该结构体共20个字节。
例4 与例3同样道理。
typedef struct DATA4 {
char a[60]; // 实际类型就是char 为1字节
int b;
short c;
}Data4;
这里直接给出答案68,大家自己尝试列出方程核内存存储图。
2) 内嵌结构体方法:
注意三点即可:
1)最大对齐字节数为两个结构体的最大类型字节数;
2)遇到内嵌结构体,它的前一个数据存储不管是否有剩余地址,都不能再用于存储内嵌结构体的成员,必须全部补0。该内嵌结构体成员计算完也一样,也要补零,不能再存储大结构体剩余的数据。(后面例子说明)
3)在算内嵌结构体所占字节数大小时,其偏移起始地址必须加上之前的偏移总量。(后面例子说明)偏移起始地址即每次算出的结果,例如第一次算时为0。
例1
typedef struct SON {
int x;
double y;
float z;
}Son;
typedef struct CONTAIN1 {
char a[2];
int b;
double c;
short d;
Son e;
}Contain1;
步骤:
1)先找两个结构体中最大类型字节数为double=8对齐。
2)开始按第一种求法正常求。
1 x 0 = 0;
4 x 1 = 4;
8 x 1 = 8;
2 x 8 =16; //至此,short是内嵌结构体的前一个数据,不管有无剩余数据都必须补0,所以还没算内嵌结构体下,内存情况为:
a a 0 0 b b b b //看上面的计算,aa后是需要补零的。
c c c c c c c c
d d 0 0 0 0 0 0 // 即上面第2)点的注意。
现在是共24字节的偏移总量。
3)开始算内嵌结构体的所占字节数。
24 + 4 x 0 = 24; //即第3)注意。计算内嵌结构体必须加上之前的偏移总量。
24 + 8 x 1 =32;
24 + 4 x 4 = 40; // 存完4个后,然后补0符合8的倍数,即44+4=48,最终答案是48。
内存的最终情况:
a a 0 0 b b b b;
c c c c c c c c;
d d 0 0 0 0 0 0;
x x x x 0 0 0 0;
y y y y y y y y;
z z z z 0 0 0 0;
例2
证明第二点的后半句话,即若结构体内嵌于一个结构体的中间(即头尾除外),算完该内嵌结构体成员,剩余地址必须补0,不能再存储大结构体的剩余数据。
typedef struct CONTAIN2 {
char a[2];
int b;
double c;
Son e; // 把Son放上来
short d;
}Contain2;
1 x 0 = 0;
4 x 1 = 4;
8 x 1 = 8; //遇到结构体了,需计算总偏移量,为4+4+8=16。
16 + 4 x 0 = 16;
16 + 8 x 1 = 24;
16 + 4 x 4 = 32; // 这里存了float的4个字节,内存情况为:
a a 0 0 b b b b;
c c c c c c c c;
x x x x 0 0 0 0;
y y y y y y y y;
z z z z ? ? ? ?;//现在的问题是,若总字节为5x8=40字节,说明问好"?"没有补0,而是可以存储大结构体的剩余数据short;若总字节数为6x8=48的话,说明后面补0了,并且新开一行存储大结构体剩余数据。而结果正是后者。即注意的第2)点是结论。
short之前的总偏移量为40。所以最后short的计算为:
40 + 2 x 0 = 40; //存2两个 其余补0满足8的倍数即最后答案为48。
// 内存最终情况:
a a 0 0 b b b b;
c c c c c c c c;
x x x x 0 0 0 0;
y y y y y y y y;
z z z z 0 0 0 0;
d d 0 0 0 0 0 0;
3 完整代码调试:
#include <stdio.h>
typedef struct DATA1 {
char name[4];
int age;
int id;
}Data1;
typedef struct DATA2 {
double a;
int b;
short c;
}Data2;
typedef struct DATA3 {
char a[9]; // 实际类型就是char 为1字节
int b;
short c;
}Data3;
typedef struct DATA4 {
char a[60]; // 实际类型就是char 为1字节
int b;
short c;
}Data4;
//内嵌结构体求法
typedef struct SON {
int x;
double y;
float z;
}Son;
typedef struct CONTAIN1 {
char a[2];
int b;
double c;
short d;
Son e;
}Contain1;
typedef struct CONTAIN2 {
char a[2];
int b;
double c;
Son e; // 把Son放上来
short d;
}Contain2;
int main() {
printf("%d\n", sizeof(Data1)); //12
printf("%d\n", sizeof(Data2)); //16
printf("%d\n", sizeof(Data3)); //20
printf("%d\n", sizeof(Data4)); //68
//内嵌求法
printf("%d\n", sizeof(Contain1));//48
printf("%d\n", sizeof(Contain2));//48
return 0;
}
4 自己设定最大内存对齐方法:
##pragma pack(2) //必须是2的倍数,最大是64位8字节。
上面表示最大对齐若超过2字节,仍以2字节计算,否则不用。
例如最大对齐是1字节,则以1字节;若最大对齐字节是4字节,以2字节对齐。
5 总结:
看到这里理解透的话,我相信你面试这种类型题基本有手就行。