引言
在刚开始学习C语言的结构体时,对于结构体内存空间的分配不甚明朗,对于不同类型变量所占空间,位置,结构体的对其方式,嵌套结构体以及pragma的使用都存在诸多疑惑。经同学讲解后得以理解,以此文章记录以便各位读者学习以及自己在遗忘之时回顾。
此文章阅读需要掌握C语言以及结构体的基本概念后进行阅读。
常用变量大小
在结构体中常出现的类型中,char占一个字节,int和float占四个字节,double占八个字节。(更多信息请自行查询)
基本结构体内存分配
将内存空间从零开始索引,结构体中每个变量按照代码运行顺序向一块内存中逐个添加,须从本结构体中最大变量类型大小的整倍数索引内存块处开始依次加入,在添加某个元素时,需要将此元素的起始填充位置置于此元素变量类型大小的整数倍。在填充完这个元素后,若没有超出一个整数倍最大变量类型大小的内存块,则直接向后填充。若超出了一个整数倍内存块,则需要从下一个整数倍内存块起始处开始继续添加,空出上一个整数倍内存块的剩余块数。其中零是所有变量类型内存大小的整倍数。
以上是自己总结,可能不甚严谨,我们来看具体的例子。
结构体内存空间分布如图。
在图示的空间中,变量在存储空间中按照索引从上往下按照顺序存储。
示例1
#include <stdio.h>
struct example1
{
char a;
int b;
float c;
double d;
} w;
int main()
{
printf("%d", sizeof(w));
return 0;
}
上述example1结构体所占内存大小为24字节。我们来开始分析。
首先此结构体最大变量大小为8,因此按照8字节对齐。在内存中放入第一个变量元素a,类型为char,占1个字节。0是8的整数倍,所以从第0号索引块开始存储并且将0号存储块填充。
接着存入b,b为4个字节,而0号索引块后的123号索引块并非4的倍数,所以从第4号索引块开始存入b,占4个字节,将7号索引块填充后存储完成,未超出8块内存,不做调整。
8是8的倍数,于是c从8号索引块开始,向后填充4个字节到11号。
最后12.13.14.15都不是8的倍数,所以d从开始16填充到23,整个结构体存储完成,其中1.2.3.12.13.14.15都是空的索引块。由于从0开始索引排序,共占24个字节。
示例2
对于存在数组的结构体,对齐时按照数组中的基本元素大小为单位进行对齐。
下面我们来看一组有数组结构体的示例
#include <stdio.h>
struct example2
{
char a[5];
int b[3];
} w;
int main()
{
printf("%d", sizeof(w));
return 0;
}
在这个结构体example2中,按照4字节对齐。包含了两个数组。首先我们将char a[5]填入内存中,占据0-4五个字节的内存。在放入Int b[3]时,会占据12个字节。在判断放入的位置时,按组成数组的基本变量大小即4字节的int来决定b的起始位置,显然5,6,7都不是4的整数倍,不符合要求,因此从8开始进行填充,到19填充完为止,共20个字节。
嵌套结构体内存分配
含结构体的结构体内存分配
将嵌套结构体看作正常元素,对齐方式按嵌套结构体的最大变量类型大小计算,所占空间不变。
示例3
#include <stdio.h>
struct example1
{
double d;
char a[5];
int b;
float c;
};
struct example2
{
char a[4];
int d;
struct example1 b;
char c[10];
} w;
int main()
{
printf("%d", sizeof(w));
return 0;
}
由上文易得,example1为24字节。在example2中,对齐方式由example1决定,为8字节对齐。前两个元素占据8个字节正好沾满无溢出,example1从8开始占据24字节到31结束,最后c从32开始到41结束,但由于8字节对齐,所以向后空出6字节凑成8的倍数,即共占48字节。
联合体内存分配
在理解含联合体的结构体内存分配之前,我们需要理解单一联合体的内存空间如何分配。
在联合体中,以最大内存的变量类型的大小a进行对齐。即为先确定结构体中最大参数的大小b,接着上调到大于b的a的整数倍。如:a=4, b=5, 内存=8; a=8, b=10, 内存=16;
示例4
#include <stdio.h>
union example3
{
float x;
char c[5];
} w;
int main()
{
printf("%d", sizeof(w));
return 0;
}
在example3联合体中,占最大内存的参数为c,5个字节。最大内存的变量类型的大小为float,占4个字节,所以应以4字节对齐。比5大的4的最小倍数为8,因此联合体内存占8字节。
含联合体的结构体内存分配
当联合体嵌入在结构体中时,对齐方式和本身大小都不发生变化。
示例5
#include <stdio.h>
union example3
{
float x;
char c[5];
};
struct example4
{
union example3 a;
char b[5];
union example3 c;
} w;
int main()
{
printf("%d", sizeof(w));
return 0;
}
在上文中,我们得知example3的大小为8字节,对齐方式为4字节。
在example4中,从0开始,a将前8字节全部填充,到7号索引。接着b占5个字节到12号索引。由于13, 14, 15都不是4的整数倍,因此从16号索引开始填充8个字节到23号索引结束,共占24个字节的内存。
pragma pack的使用
pragma可以改变结构体的对齐方式,按照pragma指定的数字字符对齐。
结构体的对齐方式取pragma pack和结构体对齐方式中较小值。
如果pragma指定的数字大于结构体中最大变量类型的大小时,pragma无效,按照最大变量类型的大小对齐。
示例6
#include <stdio.h>
#pragma pack(8)
struct example5
{
char a;
int b;
char c;
} w;
int main()
{
printf("%d", sizeof(w));
return 0;
}
此处pragma无效,正常对齐。
示例7
当pragma的值小于结构体本身的对齐大小时,按pragma pack的值进行对齐。
#include <stdio.h>
#pragma pack(2)
struct example6
{
char a;
int b;
char c;
} w;
int main()
{
printf("%d", sizeof(w));
return 0;
}
首先填入a,占0号索引。在填入b,占4个字节,但1号索引不是2的倍数,因此将1号索引块空出。b从2号索引起始,到5号截至。c从6号索引起始,占据6号索引,由于是2字节对齐,因此7号为一个填充的空字节,此结构体共占8字节。
基本的结构体内存空间大小到这里几乎就全部涵盖了,如又不当之处欢迎大家指出,会第一时间进行改正。