1.结构体类型的声明
struct stu
{
char name[20];//姓名
int age;//年龄
};
例如,一个学生拥有姓名和年龄两种信息,就可以通过结构体类型对一个学生相关的两个变量进行描述。
创建完结构体变量后,就可以进行初始化:
#include <stdio.h>
struct stu
{
char name[20];//姓名
int age;//年龄
};
int main()
{
//按顺序初始化
struct stu s = {"张三", 18};
//指定顺序初始化
struct stu s1 = {.age = 21, .name = "李四"};
return 0;
}
结构体在声明时,可以不对结构体类型进行重命名,例如省略上面代码中的stu,这样做的后果就是匿名的结构体类型只能用一次。
结构体可以做到自引用,但是不是直接把结构体变量包含进结构体内部,而是借助指针:
struct stu
{
int age
struct stu* next;
};
2.结构体内存对齐
结构体在内存中储存时有一定的规则:
- 结构体的第一个成员对齐到和结构体变量起始位置偏移量为0的地址处;
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处;//对齐数指编译器默认的对齐数 与 该成员变量大小的较小值
- 结构体总大小为最大对齐数(每一个成员都有对齐数,取所有对齐数中的最大值)的整数倍;
- 如果嵌套了结构体的情况,嵌套的结构体成员对齐到自己的成员中最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体中成员的对齐数)的整数倍
只看以上文字有些云里雾里的,我们需要通过实战来感受结构体的内存对齐,例如:
struct s1
{
double d;
char c;
int i;
};
首先,d在内存偏移量为0的地方开始,由于d是double类型,因此d的对齐数为8个字节,同理c的对齐数为1个字节,i的对齐数为4个字节。那么c就应该在内存偏移量为8的地方,而i为了对齐到4的整数倍的地址处,应该在内存偏移量为12的地方,那么该结构体内存大小应该是16个字节。
接下来再来考虑一下嵌套的情况:
struct s2
{
char c;
struct s1 s;
double d;
};
c的对齐数是1,s的对齐数则是s1中的最大对齐数也就是8,d的对齐数也是8。c在内存偏移量为0的地方,s为了对齐,在内存偏移量为8的地方,而由于s1的内存大小是16个字节,那么d在内存偏移量为24的地方,最后结构体s2的内存大小应该为32个字节。
为什么会有内存对齐呢,有平台方面的原因(方便代码移植),也有性能方面的原因(减少访问次数),总的来说,是一种用空间换时间的做法。
默认对齐数是可以修改的,前文由于作者使用的是vs,默认对齐数为8,double的对齐数就是8,而使用代码,例如:#pragma pack(1),就可以将默认对齐数修改为1,此时结构体就不存在内存对齐了。