目录
一、什么是结构体
结构体是将不同类型的数据按照一定的功能需 求进行整体封装,封装的数据类型与大小均可以由用户指定。
结构体定义,如下图1:
图1
二、为什么需要结构体
当需要存储大量重复的信息时,比如存储一个班级学生的信息,肯定包括姓名、学号、性别、年龄、成绩、家庭地址等项。这些项都是具有内在联系的,它们是一个整体,都表示同一个学生的信息。但如果将它们定义成相互独立的变量的话,就无法反映它们的内在联系,如下图2:
图2
如果只是这样写的话,只是定义了一个学生,如果要定义第二个学生就要再写一遍。这样不仅麻烦,而且很容易混淆。要是能定义一个变量,而且这个变量正好包含这六个项,即将它们合并成一个整体的话就好了。结构体就是为了解决这个问题而产生的。
三、结构体的字节对齐
现在来探讨一个问题,结构体的大小是如何划分的呢?
3.1、示例1
有这样一个程序,结构占用内存大小按理来说应该是,char占1个字节,int占4个字节,1+4=5,结构体占用内存大小应该是5个字节才对。
#include <stdio.h>
struct STUDENT
{
char a;
int b;
}data;
int main()
{
printf("a的地址为:%p,b的地址为:%p\n", &data.a, &data.b); //%p是取地址符
printf("结构体总大小为:%d\n", sizeof(data));
return 0;
}
以上程序输出结果如下图3所示:
图3
内存分配图下图4:
图4
分析:
事实上结构体大小是8个字节,b占4字节我们能理解,但a是char型,char型不是占1字节吗,这里为什么占4字节?其实不是它占了4字节,它占的还是1字节,只不过结构体中有一个字节对齐的概念。
那么到底是怎么存储的呢?
就是按字节对齐的方式存储的!即以结构体成员中占内存最多的数据类型所占的字节数为标准,所有的成员在分配内存时都要与这个长度对齐。
以上面这个程序为例,结构体变量data的成员中占内存最多的数据类型是int型,其占4字节的内存空间,那么所有成员在分配内存时都要与4字节的长度对齐。也就是说,虽然char只占1字节,但是为了与4字节的长度对齐,它后面的3字节都会空着。
3.2、示例2
按上面的结论,最大的是int占4个字节,那么按4字节对齐,大小应该是4+4+4=12;
#include <stdio.h>
struct STUDENT
{
char a;
cahr c;
int b;
}data;
int main()
{
printf("a的地址为:%p,b的地址为:%p,c的地址为:%p\n", &data.a, &data.b, &data.c);
printf("结构体总大小为:%d\n", sizeof(data));
return 0;
}
结果如下图5:
图5
但大小实际上并没有变化,内存分配为下图6所示:
图6
分析:
在分配了a的内存后,c希望和a近邻,并且因为内存对齐a申请了4个字节,在a相邻的位置可以存放,所以c与a共同占用一个4字节内存。
3.3、示例3
首先最长的数据类型占4字节,所以是以4字节对齐。1+1+1+1+4+4=12
#include <stdio.h>
struct STUDENT
{
char a;
char c;
char d;
char f;
char e;
int b;
}data;
int main()
{
printf("a的地址为:%p,b的地址为:%p,c的地址为:%p,d的地址为:%p,f的地址为:%p,e的地址为:%p\n", &data.a, &data.b, &data.c, &data.d, &data.f, &data.e);
printf("结构体总大小为:%d\n", sizeof(data));
return 0;
}
输出结果如下图7:
图7
即该结构体变量分配内存时如下图8:
图8
分析:
首先最长的数据类型占4字节,所以是以4对齐。a占1字节,b接在a后面占1字节,c接在b后面占1字节,d接在c后面占1字节,此时满4字节了,e再来就要另起一行。f想紧接着e后面分配,但e后面还剩3字节,小于int类型的4字节,所以f另起一行。
3.4、示例4
首先最长的数据类型占4字节,所以是以4字节对齐。4+4+4=12。
#include <stdio.h>
struct STUDENT
{
char a;
int c;
char b;
}data;
int main()
{
printf("a的地址为:%p,b的地址为:%p,c的地址为:%p\n", &data.a, &data.b, &data.c);
printf("结构体总大小为:%d\n", sizeof(data));
return 0;
}
输出结果如下图9:
图9
内存分配如下图10:
图10
分析:
首先最长的数据类型占4字节,所以是以4对齐。a占1字节,c希望与a近邻,但3字节不足以存放int,故重新再起一行,所以a占4字节。b也希望与a近邻,但a空间不足以存放b,故b也重新起一行占4个字节。
3.5、示例5
首先最长的数据类型占4字节,所以是以4字节对齐。但出现了数组,大小应该怎样计算呢?
#include <stdio.h>
struct STUDENT
{
char name[3];
int age;
char sex;
char score;
}data;
int main()
{
printf("%p,%p,%p,%p,%p,%p\n", &data.name[0], &data.name[1], &data.name[2], &data.age, &data.sex, &data.score);
printf("结构体总大小为:%d\n", sizeof(data));
return 0;
}
输出结果如下图11:
图11
分析:
出现了数组,数组可以看做多个char类型,依然是占1字节,所以内存大小为:4+4+4=12。
四、结构体字节对齐总结
概念总结:
1)可以知道,同样的数据类型,只不过交换了一下位置,结构体变量data所占的内存空间就由8字节变成12字节,多了4字节。这就告诉我们,在声明结构体类型时,各类型成员的前后位置会对该结构体类型定义的结构体变量所占的字节数产生影响;
2)没有规律的定义会增加系统给结构体变量分配的字节数,降低内存分配的效率。但这种影响对操作系统来说几乎是可以忽略不计的!所以我们在写程序的时候,如果有心的话,声明结构体类型时就按成员类型所占字节数从小到大写,或从大到小写;
3)但是如果没有按规律书写的话也不要紧,声明结构体类型时并非一定要从小到大声明,只是为了说明结构体字节对齐这个概念。
使用总结:
1)按占用最大的字节对齐;
2)占用内存可以共用(前提是足以同时保存);
3)不足以存放时,会出现申请一个内存空间;
4)合理书写顺序可以节省CPU资源,当量足够大时,有可能会影响系统运行效率。
如果在单片机使用RTOS,RTOS管理的内存空间固定,但因为结构体申请了很多内存,在使用时,有可能因为内存不足而产生不必要的错误。