1.什么是结构体
C语言给我们提供了一些常见的数据类型,例如字符串char、整形int、浮点型float等。但是在实际编程中,这些简单的数据类型不能满足一些特定的需求。比如对于一个学生管理系统,我们关心每个学生的名字、学号、年龄和性别,我们用不同的数据类型对每个学生的不同信息都进行申明和操作就会显得格外的麻烦。结构体就能够帮助我们简化这个问题。结构体是一些值的集合,这些值称为成员变量,每个成员变量可以是不同类型的变量。这里要与数组进行区分一下,数组是同一类型的数据集合。我们用C语言声明一个结构体的代码如下:
struct Stu
{
char name[20];//名字
char id[20];//学号
int age;//年龄
char sex[5];//性别
}; //分号不能丢
当然,对于结构体的其他特殊声明方式(比如匿名结构体等)不做过多讲解。
2.结构体内存对齐
我们都知道C语言的一般数据类型都会有固定的字节大小,也就是在内存中占用多少内存,比如char占一个字节,short int 占两个字节。那么一个结构体到底多大呢?起初,我一直认为结构体的大小就是所有的成员变量的大小之和,但后面发现这是错误的。结构体的大小是由结构体的内存对齐决定的,也就是结构体的内存大小计算有一套严格的规则,结构体对齐规则如下:
1.第一个成员在结构体变量偏移量为0的地址处。(在画图计算时候可以认为随便开始,记得标为0就是)
2.从第二个成员开始就需要对其到对齐数的整数倍的地址处。
对齐数 = min{该编译器默认对齐数,该成员大小} VS编译器中默认对齐数是8
3.结构体的总大小还必须是该结构体成员中占字节数最大的那一个的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己最大对齐数(自己成员中占字节数最大的成员变量的字节大小)的整数倍处,结构体的总大小是最大对齐数(包括嵌套结构体)的整数倍。
下面举两个例子计算一下。
struct S1
{
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));
该结构体的大小为12,计算过程如下
为了让读者理解更加深刻一些,再举一个例子。
struct S2
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));
该结构体的大小为8,计算过程如下
通过上面的计算就可以发现,结构体的内存对齐会浪费很多的内存,那么为什么要这样牺牲内存呢?
3.存在内存对齐的原因
通过查阅资料,发现C语言官方并没有给出明确的解释,大部分参考资料都是说的如下两个原因:
1.平台原因,不是所有的硬件平台都能随意访问任意地址上的任意数据,某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。这个原因我们可以这样理解,就是大家从小到大都接触到的天平,假设每个砝码是一公斤,如果单个货物都是一公斤或者两公斤等,那么我就能用一定数量的砝码称出货物的重量。但是要是我们的货物都是几克,那么我们的砝码就不能说一定能够称出重量来,
2.数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
所以,总体来说,结构体的内存对其是拿空间换时间的做法,这样能够提高效率。
4.结构体设计技巧
从上面的分析可以看到,结构体成员的位置先后布置可以影响结构体的大小,而结构体的内存对齐方式是拿空间换时间。那么,如果我们合理的利用规则就可以使得结构体大小最小化,也就是说我们在设计结构体的时候,可以利用结构体内存对齐规则反向优化结构体的内存空间。一般来说,把字节数少的结构体成员放在前面会很大程度上优化内存空间。比如前面计算用到的两个例子,把char类型的放在前面就节约了4个字节的空间。