一,结构体(struct)内存数据储存方式
1.引子
让我们先来看一组代码吧。
#include <stdio.h>
struct s
{
char c1;//1
int i;//4
char c2;//1
};
int main()
{
int a = sizeof(s);
printf("%d", a);//1+4+1=6??
return 0;
}
这段代码的输出结果是多少呢,一个整型加两个字符的字节大小,是6吗?
很遗憾,答案是12,而这又是怎么一回事呢?其实,这是因为结构体有着自己独特的储存数据的方式!
2.结构体内存对齐
对于结构体的内存储存来说,结构体的数据储存始终遵循这三个原则:1,结构体第一个成员从偏移量为0的地址处对齐。2,其他成员对齐到对齐数的整数倍地址处。3,结构体的总大小为最大对齐数的整数倍。4,若出现结构体的嵌套,则嵌套成员要对齐到其本身最大对齐数的整数倍处。
如果第一次读到这三条规则,想必你一定会感到非常迷糊。没事,我刚开始时也一样,但当你了解了对齐数是什么之后,这些疑惑也就迎刃而解了。
对齐数=编译器默认的一个对齐数与该成员变量字节大小的较小值。
——vs编译器中默认对齐数为8。
——Linux中gcc无默认对齐数,对齐数为成员自身大小。
例如int类型,大小为四个字节,在vs编译器中,4小于编译器默认对齐数8,那么int类型的对齐数则为4。
而最大对齐数,顾名思义,则为所有对齐数中最大的那一个咯。
3.引子例题分析,加深理解
让我们回到引子中的例题上去。当编译器拿到这个结构体是,则就会在内存中申请一段空间。
那么根据结构体储存数据第一条规则,char变量则会储存到内存偏移量为零的0地址处。
然后int类型呢,我们知道它的对齐数为4,那么它就需要储存到4的整数倍地址处,也就是地址为4的地方。
最后就又是char了,char类型的对齐数是1,则它可以储存到1的整数倍处,但是勿忘规则三,结构体的总大小应该是最大对齐数的整数倍,而在这里的最大对齐数是4,那么我们应该将最后一个char储存后,结构体总大小为12,那么编译器会将char储存到地址为11的地方。注:是结构体总大小为最大对齐数的整数倍,而不是储存到地址为最大对齐数的整数倍的地方!
所以,显而易见,该结构体struct占用了0-11的地址内存,那么大小就是12了。
4.拓展:编译器默认对齐数的更改
其实编译器的默认对齐数可以通过自己手写代码修改。
#pragma pack(1)//默认对齐数改为1
#pragma pack//默认对齐数的还原
通过这些代码,我们就可以自己动手修改对齐数了。
二,联合体(union)内存数据储存方式
1.联合体大小
联合体中,所有成员共用一块空间,这也是联合体也被称为共用体的原因。1,联合体的大小至少为成员大小。2,当最大成员大小不为最大对齐数的整数倍时,要对齐到最大对齐数的整数倍。
例子:
union s
{
char c[5];//1
int i;//4
};
那么这段代码中,联合体s的大小为多少呢?
当编译器拿到联合体s后,则也同样会开辟一段空间。
所以我们先放入占入字节数为5的字符数组。
因为这是联合体,所以int将会和c[5]共用同一块空间。
这样看来我们的联合体的大小为5对吧,但是别忘了规则二,我们需要将联合体对齐到最大对齐数4的整数倍的地方,所以我们的联合体空间应该开辟到地址为7的地方,0-7,则联合体的总大小为8。
2.联合体共用空间的演示
我们在编译器中输入以下代码。
#include <stdio.h>
union s
{
char c;//1
int i;//4
}s;
int main()
{
s.i = 0x11223344;
s.c = 0x55;
return 0;
}
然后我们进入到调试阶段,当我们调试到s.i=0x11223344;时,内存部分很好的存入了这个十六进制数字。
当我们继续调试呢?
看,原来的值的最低位数字被改为了55。这也不就正好的说明了联合体的内存是共用的吗,int占用了四个字节,char占用了一个字节,而这一个字节正好就是int中的一个字节,改变了char也就同时改变了int。
三,结语
当然,结构体和联合体这么储存数据也不是没有它的道理的,设计者其实是想将内存分为几块,这样取出数据也就更快。相当于设计者是想要牺牲一部分空间来换取更加快的编译器处理速度。
ok,这就是这篇blog我想要表达的内容了,如有错误,希望得到指正,虚心学习,加油加油!