前言
我们之前学的char、int、float这些,都是基础类型,一个变量只能存一个数据。 但现实里很多东西是一组相关数据,比如学生:姓名、年龄、学号、分数。 单独用好几个零散变量存很麻烦,结构体(struct)就是用来把一堆不同类型的数据打包在一起的自定义类型。
一、结构体类型怎么定义
语法格式:
struct 结构体名 { 成员1; 成员2; 成员3; };举个学生的例子:
// 定义一个学生结构体类型 struct Student { char name[20]; // 姓名 int age; // 年龄 float score; // 分数 };注意:上面这段代码只是创建了一个新类型,没有开辟内存,没有变量,就像是图纸,不是房子。
二、结构体变量的创建和初始化
方式1:定义结构体之后,单独创建变量
struct Student s1; // 初始化 struct Student s2 = {"zhangsan", 18, 92.5f};方式2:定义结构体的同时直接创建变量
struct Student { char name[20]; int age; float score; } s3, s4; // s3 s4 就是结构体变量方式3:匿名结构体(没有结构体名字)
只能在这里直接创建变量,后面不能再新建这个类型的变量
struct { int a; char b; } data;访问结构体成员:.点操作符
#include <stdio.h> struct Student { char name[20]; int age; float score; }; int main() { struct Student s = {"lisi", 19, 88.0f}; printf("名字:%s 年龄:%d 分数:%.1f\n", s.name, s.age, s.score); return 0; }运行结果:
名字:lisi 年龄:19 分数:88.0如果是结构体指针,就用->箭头访问成员,这个后面再拓展,本篇重点是内存对齐。
三、重点:结构体内存对齐
很多人以为结构体大小 = 所有成员大小直接相加,这个想法是错的!CPU读取内存的时候,喜欢按固定块读取,对齐就是为了提升读取效率,代价是浪费一点点空间(填充空白字节,叫填充/补齐padding)。
内存对齐三条核心规则(记住这三条就够)
- 结构体第一个成员,放在偏移0的位置(最开头)
- 后面每个成员,要对齐到自身大小和默认对齐数里较小值的整数倍地址
- 结构体的总大小,必须是结构体最大成员大小的整数倍,不够就在末尾补padding
VS默认对齐数是8,Linux gcc默认对齐数也是8
例子1:简单结构体,画图讲解
struct Test1 { char c; // 1字节 int i; // 4字节 };我们来一步步算,画内存格子(一个格子代表1字节)
地址: 0 1 2 3 4 5 6 7 数据:[c] 空 空 空 [i0][i1][i2][i3]c放在0号地址,占1字节
int占4字节,它要放在4的倍数地址,下一个可用地址是1,不是4倍数,所以1、2、3填充空白padding
i从地址4开始,占4字节,到7结束
最大成员是int(4字节),总大小要4的倍数,现在一共8字节,刚好满足
sizeof(struct Test1) = 8,不是 1+4=5!
代码验证:
#include <stdio.h> struct Test1 { char c; int i; }; int main() { printf("%zd\n", sizeof(struct Test1)); // 输出 8 return 0; }例子2:换一下成员顺序,大小直接变了!
struct Test2 { int i; // 4字节 char c; // 1字节 };内存排布:
地址: 0 1 2 3 4 5 6 7 数据:[i0][i1][i2][i3] [c] 空 空 空int从0开始,占0~3
char放地址4,占1字节
最大成员是4字节,总大小必须是4倍数,现在5字节,后面补3个空字节,凑到8
sizeof(struct Test2) = 8
小结论:调整成员顺序,可以减少内存浪费!
例子3:再复杂一点的组合
struct Test3 { char a; // 1 short b; // 2 int c; // 4 };内存排布:
0:[a] 1:空 2:[b0][b1] 4:[c0][c1][c2][c3]a在0
short是2字节,要放到2的倍数地址,地址1不行,地址2可以,1位置补padding
int放到4的倍数地址4
总大小8,刚好是最大成员4的倍数 ✅ sizeof = 8 总和直接相加 1+2+4=7,对齐之后变成8
例子4:嵌套结构体对齐(拓展了解)
规则不变,嵌套的结构体,对齐数取它内部最大成员的大小
struct Inner { char ch; int num; }; struct Outer { char x; struct Inner in; };Inner大小是8,Inner最大成员是4。 in成员要对齐到4的倍数地址。 x在0,1、2、3补padding,in从4开始放,总大小 4+8=12。
四、修改默认对齐数(#pragma pack)
我们可以手动改对齐数,用来省空间,在一些硬件开发里经常用到。
#pragma pack(1) // 设置对齐数为1,关闭对齐,不补padding struct Test { char c; int i; }; #pragma pack() // 恢复默认对齐pack(1)的时候,上面这个结构体大小就是5,不会补空字节。 不推荐随便改,会降低CPU访问速度,只有特殊场景才用。
五、小结
struct结构体,是C给我们自定义打包数据的工具,把一堆不同类型成员放一起。
结构体变量创建有三种写法,
.访问成员,指针用->。结构体大小不能直接成员相加,要遵守三条对齐规则,中间和末尾会填充空白字节padding。
成员顺序会影响结构体占用内存大小,合理排序可以节省内存。
#pragma pack可以修改对齐数,慎用。