前言结构体是 C 语言用于封装多类型数据的核心语法能够将不同类型的数据成员整合为一个整体极大提升代码的结构化与可读性。相比于基础数据类型结构体更适合描述复杂实体数据。同时结构体的内存对齐、内存大小计算是 C 语言底层重点难点。本文系统讲解结构体的声明、创建、初始化并深入剖析内存对齐原理帮助大家全面掌握结构体核心知识。一. 结构体类型的声明结构体的声明struct tag { member-list; }variable-list;例如描述一个学生struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 };结构的特殊声明//匿名结构体类型 struct { int a; char b; float c; }x; struct { int a; char b; float c; }a[20], *p;上⾯的两个结构在声明的时候省略掉了结构体标签tag注意p x;是不合法的编译器会把上⾯的两个声明当成完全不同的两个类型匿名的结构体类型如果没有对结构体类型重命名的话基本上只能使⽤⼀次二. 结构体变量的创建和初始化#include stdio.h struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; int main() { //按照结构体成员的顺序初始化 struct Stu s { 张三, 20, 男, 20230818001 }; printf(name: %s\n, s.name); printf(age : %d\n, s.age); printf(sex : %s\n, s.sex); printf(id : %s\n, s.id);//按照指定的顺序初始化 struct Stu s2 { .age 18, .name lisi, .id 20230818002, .sex ⼥ }; printf(name: %s\n, s2.name); printf(age : %d\n, s2.age); printf(sex : %s\n, s2.sex); printf(id : %s\n, s2.id); return 0; }三. 结构体内存对齐结构体的对齐规则1. 结构体的第⼀个成员对齐到和结构体变量起始位置偏移量为0的地址处2. 其他成员变量要对⻬到某个数字对齐数的整数倍的地址处。对齐数编译器默认的⼀个对齐数与该成员变量大小的较小值。V S 中默认的值为 8Linux中gcc没有默认对齐数对齐数就是成员自身的大小3. 结构体总大小为最大对齐数结构体中每个成员变量都有⼀个对齐数所有对齐数中最大的的 整数倍。4. 如果嵌套了结构体的情况嵌套的结构体成员对⻬到自己的成员中最大对齐数的整数倍处结构 体的整体大小就是所有最大对齐数含嵌套结构体中成员的对齐数的整数倍。例子1struct S1 { char c1; int i; char c2; }; printf(%d\n, sizeof(struct S1));内存中结构体的存储c1 为结构体第一个成员对齐到结构体变量起始位置偏移量为0 的地址处char 只占1个字节int 变量大小为4个字节vs 默认的对齐数是8 所以i 变量的对齐数为两者较小值 4i 对其到 4 的整数倍地址处所以i 的地址从偏移量 4 开始char 变量大小为1个字节vs 默认的对齐数是8 所以c2 变量的对齐数为两者较小值 1c2 对其到 1 的整数倍地址处所以i 的地址从偏移量 9 开始结构体总大小为最大对齐数的整数倍c1c2 的对齐数为1i 的对齐数为4则最大对齐数为4所以结构体 s1 的总大小为 12。例子2struct S2 { char c1; char c2; int i; }; printf(%d\n, sizeof(struct S2));内存中结构体的存储c1 为结构体第一个成员对齐到结构体变量起始位置偏移量为0 的地址处char 只占1个字节char 变量大小为1个字节vs 默认的对齐数是8 所以c2 变量的对齐数为两者较小值 1c2 对其到 1 的整数倍地址处所以i 的地址从偏移量 1 开始int 变量大小为4个字节vs 默认的对齐数是8 所以i 变量的对齐数为两者较小值 4i 对其到 4 的整数倍地址处所以i 的地址从偏移量 4 开始结构体总大小为最大对齐数的整数倍c1c2 的对齐数为1i 的对齐数为4则最大对齐数为4所以结构体 s1 的总大小为 8。例子3struct S3 { double d; char c; int i; }; printf(%d\n, sizeof(struct S3));内存中结构体的存储d 为结构体第一个成员对齐到结构体变量起始位置偏移量为0 的地址处double 占8个字节char 变量大小为1个字节vs 默认的对齐数是8 所以c 变量的对齐数为两者较小值 1c 对其到 1 的整数倍地址处所以i 的地址从偏移量 8 开始int 变量大小为4个字节vs 默认的对齐数是8 所以i 变量的对齐数为两者较小值 4i 对其到 4 的整数倍地址处所以i 的地址从偏移量 12 开始结构体总大小为最大对齐数的整数倍double 的对齐数为8c 的对齐数为1i 的对齐数为4则最大对齐数为8所以结构体 s1 的总大小为 16。例子4//结构体嵌套 struct S4 { char c1; struct S3 s3; double d; }; printf(%d\n, sizeof(struct S4));内存中结构体的存储c 为结构体第一个成员对齐到结构体变量起始位置偏移量为0 的地址处char 只占1个字节s3 变量大小为16个字节嵌套的结构体成员中最大的对齐数为 8vs 默认的对齐数是8 所以c 变量的对齐数为两者较小值 8s3 对其到 8 的整数倍地址处所以i 的地址从偏移量 8 开始double 变量大小为8个字节vs 默认的对齐数是8 所以i 变量的对齐数为两者较小值 8i 对其到 8 的整数倍地址处所以i 的地址从偏移量 24 开始结构体总大小为最大对齐数的整数倍s3 的对齐数为8double 的对齐数为8c 的对齐数为1则最大对齐数为8所以结构体 s1 的总大小为 32。为什么存在内存对齐大部分的参考资料都是这样说的1. 平台原因(移植原因) 不是所有的硬件平台都能访问任意地址上的任意数据的某些硬件平台只能在某些地址处取某些特定 类型的数据否则抛出硬件异常。2. 性能原因 数据结构(尤其是栈)应该尽可能地在⾃然边界上对⻬。原因在于为了访问未对⻬的内存处理器需要 作两次内存访问⽽对⻬的内存访问仅需要⼀次访问。假设⼀个处理器总是从内存中取8个字节则地 址必须是8的倍数。如果我们能保证将所有的double类型的数据的地址都对⻬成8的倍数那么就可以 ⽤⼀个内存操作来读或者写值了。否则我们可能需要执⾏两次内存访问因为对象可能被分放在两 个8字节内存块中。总体来说结构体的内存对⻬是拿空间来换取时间的做法。那在设计结构体的时候我们既要满⾜对⻬⼜要节省空间如何做到//让占⽤空间⼩的成员尽量集中在⼀起struct S1 { char c1; int i; char c2; }; struct S2 { char c1; char c2; int i; };S1 和 S2 类型的成员⼀模⼀样但是 S1 和 S2 所占空间的⼤⼩有了⼀些区别。修改默认对齐数#pragma 这个预处理指令可以改变编译器的默认对⻬数。#include stdio.h #pragma pack(1)//设置默认对⻬数为1 struct S { char c1; int i; char c2; }; #pragma pack()//取消设置的对⻬数还原为默认 int main() { //输出的结果是什么 printf(%d\n, sizeof(struct S)); return 0; }结构体在对⻬⽅式不合适的时候我们可以⾃⼰更改默认对⻬数。