一.结构体在内存中的存储方式
假设我们有这样一个结构体(注:下面提到的偏移量就相当于是内存中的地址)
struct Student
{
int a;
char b;
short c;
};
你觉得这个结构体在内存中占用多少个字节的内存呢?是不是int+char+short=4+1+2=7呢?实际上,结构体在内存中如果是连续存放的话就可能会导致多次触发内存操作,导致性能下降。
那结构体在内存中到底是如何访问的呢?这就要提到结构体的对齐规则了
-
对齐要求:每个成员的起始地址必须是其类型大小或编译器对齐值的整数倍(取较小者)。
-
例如:
int
(4字节)需从4的倍数的地址开始。
-
-
填充字节:编译器可能在成员间插入空白字节以满足对齐要求。
-
结构体总大小:必须是最宽成员大小的整数倍(或编译器指定的对齐值),不足时末尾填充。
1)什么是成员的起始地址必须是其类型大小或编译器对齐值的整数倍呢?这就要扯上偏移量了,先上一张图让我们更好的看出啥是偏移量,假设该结构体在内存中的存储的起始地址是从0开始的,那a在内存就是从偏移量0开始存储的(注:第一个变量都是从偏移量为零的地方开始存储的,不用考虑对齐规则),a是占4个字节,从偏移量为0的地方向下数4个位置,从偏移量为0到偏移量为3的地方就是a在内存中的存储空间。
2)b变量是char型,大小是1个字节,它在内存中是怎么存储的呢?从第二个成员开始就需要按照对齐规则来存储了,有些编译器的对齐数是4,也有些是8,我们这里根据对齐数为8的情况下讨论,char是占1个字节比对齐数小,就看偏移量是否为b大小的整数倍了,可以看到1是任何数的倍数,所以b直接存放在a的后面,也就是偏移量为4的位置。
3)c是short型变量占2个字节,对齐数取c的大小2,c要存储只能从偏移量为5的地方开始存储,但是5不是2的倍数,就直接跳过该位置,然后再看该位置是否为2的倍数,如果是则从该偏移量位置开始存储,在这里c是从偏移量为6的位置开始存储。
最后知道了他们在内存中的存储之后如何计算整个结构体的大小呢?在规则中说最宽成员的整数倍,也就是哪个最大,显而易见a是最大的占4个字节,先看当前结构体在我们想象中的大小是否为4的倍数,可以看到从偏移量为0到7是8个字节,是4的整数倍,那该结构体的大小就为8个字节。
还有另外一种情况就是最宽成员变量的空间大小不是我们想象中结构体空间的倍数,此时就需要在末尾填充到空间为最宽成员的整数倍,在这里我给一个例子,大家自己去想一下,这个结构体占多少个字节的空间。
struct Peo
{
char a;
int b;
short c;
};
二.联合体在内存中的存储规则
先看规则和原理:
. 内存共享与覆盖
-
共享内存:联合体的所有成员共用同一块内存地址,同一时刻只能存储一个成员的值。
-
覆盖写入:修改一个成员的值会覆盖其他成员的数据(具体覆盖范围取决于成员类型的大小)。
-
总内存大小:联合体的总大小等于最大成员的大小,且需满足对齐要求。
注:联合体的总大小必须是其对齐值的整数倍,同时至少能容纳最大的成员。
假设有一个联合体
union Example {
int a; // 4字节(对齐要求4)
double b; // 8字节(对齐要求8)
char c[10]; // 10字节(对齐要求1)
};
因为联合体的空间是共享的,所以按当前逻辑来说当前联合体的大小为10个字节,但是由于联合体的总大小为其成员中最大对齐数的整数倍,在当前联合体中,最大对齐数为8,因为double的大小为8,所以联合体大小必须为8的倍数,即当前联合体总大小为16。
三.总结
结构体与联合体都存在着对齐规则,但两种对齐规则有相似也有不同,这些规则都是为了更高效率的访问成员而设计的。更多细节请自行查找资料,欢迎在评论区讨论。