结构体想必大家都不陌生,简单来讲:结构体就是一些值的集合,这些值被称为成员变量,结构体的每个成员可以是不同类型的变量。
而关于结构体在内存中存储时所占空间的大小的计算却有一个专有名词:内存对齐。
接下来我就通过几个例子向大家阐明一下关于结构体内存对齐的那些事儿。
- 结构体对齐的规则
第一个成员在结构体变量偏移量为0。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数= 编译器默认的一个对齐数与该成员大小的较小值。
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
只是单独的阐述一下概念相信谁都没办法理解,那接下来我就为大家举上几个“栗子”来深入了解一下吧!
“栗子”1.
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n",sizeof(struct S1));
首先我们观察,当存储这个结构体的时候,我们第一个先存储的是char类型的c1,char类型占一个字节的空间,所以在内存中占据了一个空格。也就是图中的粉色部分,也对应了对齐规则的第一条:第一个成员在结构体变量偏移量为0。存储到了第一个也就是编号为0的内存中,也就是偏移量为0的意思。
然后我们再观察下一个成员,类型为int的i,也就是图中的红色部分。int类型大小为4字节,但在图中我却空出了1,2,3,三个字节的空间,原因就是对齐规则的第二条:其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
而对齐数的概念我也在对齐规则中有所提及:对齐数= 编译器默认的一个对齐数与该成员大小的较小值。
而编译器默认的对齐数是根据编译环境的不同而不同的,我使用的是vs,在vs这个编译环境中这个值就是8。较小值在相比之下就是4,而4的整数倍最小就是4,要求对齐到其整数倍地址处,所以他越过了中间三个字节自动对齐到了编号为4的字节处。又因为int类型大小为4字节,所以占据了4个空格,代表其四个字节的大小。
接下来我们再观察下一个成员,类型为char的c2,也就是图中的橙色部分。同样遵循对齐规则的第二条,char类型大小为1字节,整数倍为任意整数,故而存储时也是接着int i继续存储,没有浪费空间。至此所有成员全部存储完毕。
最后让我们看一看对齐规则的第三条:结构体的总大小为最大对齐数(每个成员都有自己的一个对齐数)的整数倍。很显然,c1,i,c2的对齐数分别为:1,4,1。最大对齐数就是4,4的整数倍4和8显然都已经被占上了,所以12就是4的最小整数倍, 所以我们补充上了到12的空白部分,也就是黄色的部分。最后从0到12就是整个结构体所占内存的大小——12字节。
“栗子”2.
struct s2
{
char c1;
struct s1 s1;
double d;
};
printf("%d\n",sizeof(s2));
在“栗子”2中,char c1与double d的存储方式与“栗子”1中尽数相同,在此我就不过多赘述,在这里我想通过这次成员中的struct s1 s1来向大家解释一下对齐规则的第四条:如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在s2中嵌套的结构体,也就是s1中最大的对齐数是4,那么在嵌套情况下s1的对齐数也就是4,因此我们将s1存储到4的整数倍,也就是编号为4的空间中,又因其大小为12字节,故占12个字节的空间。最后整体s2的结构体大小的求法和正常的求法也是相同的,但需要注意的一点就是在s2这个结构体中,所有对齐数同样也包括s1的对齐数,所以在求最大对齐数时千万不要忘了s1中的对齐数。
最后程序运行结果如下:
以上就是我对结构体内存对齐这一知识点的总结,倘若还有遗漏或不足之处还请各位大佬指出。