C语言中结构体和共用体是两种重要的复合数据类型它们能把不同类型的数据组合在一起但在内存使用方式上有着本质区别。下面我们从基本概念到高级特性系统地梳理一下。一、结构体struct结构体允许我们将逻辑上相关但类型不同的数据项聚合为一个整体。例如描述一个学生的信息需要学号整型或字符串、姓名字符串、成绩浮点型等。1. 定义与声明// 方式一先定义结构体类型再声明变量 struct Student { int id; char name[20]; float score; }; struct Student stu1, stu2; // 方式二定义类型的同时声明变量 struct Point { int x; int y; } p1, p2; // 方式三匿名结构体只能使用这一次 struct { int a; char b; } anonymousVar;通常我们会用typedef为结构体类型起别名方便使用typedef struct { int day; int month; int year; } Date; Date today;如果结构体需要自引用如链表节点则必须使用带标签的写法typedef struct Node { int data; struct Node *next; // 这里不能直接写 Node*因为别名尚未定义完 } Node;2. 成员访问通过结构体变量访问使用点运算符.通过结构体指针访问使用箭头运算符-struct Student s {1001, Alice, 92.5}; struct Student *ps s; printf(%d\n, s.id); printf(%s\n, ps-name); // 等价于 (*ps).name结构体允许整体赋值同类型struct Student t s; // 逐一复制每个成员3. 内存对齐结构体的大小并不是成员大小的简单累加编译器会为了提高存取效率而进行内存对齐。每个成员在内存中的起始地址必须是自身大小或其对齐值的整数倍。结构体的总大小必须是其最大对齐值的整数倍通常是最宽基本类型成员的大小。不同成员之间可能会插入填充字节padding。struct Example { char c; // 1 字节 int i; // 4 字节 short s; // 2 字节 }; // 实际布局假设4字节对齐 // c 占1字节随后3字节填充i 占4字节s 占2字节最后2字节填充使总大小为4的倍数 // 总大小 1 3(pad) 4 2 2(pad) 12 字节可以使用#pragma pack(n)或__attribute__((packed))改变对齐方式但可能会降低性能或导致某些硬件访问异常。4. 位域Bit-field位域允许我们以“位”为单位指定成员占用的二进制位数常用于节省空间的标志位存储或硬件寄存器映射。struct Flags { unsigned int isReadable : 1; unsigned int isWritable : 1; unsigned int isExecutable: 1; unsigned int reserved : 5; }; // 该结构体仅占1个unsigned int通常4字节但只用其中8位位域的成员通常只能是_Bool,int,unsigned int等整型。不同编译器对位域的存储顺序从低位到高位还是相反可能有差异跨平台需注意。5. 自引用结构体通过包含指向自身类型的指针可以构建链表、树等动态数据结构。struct TreeNode { int value; struct TreeNode *left; struct TreeNode *right; };6. 柔性数组成员C99结构体的最后一个成员可以是未指定大小的数组称为柔性数组。它不占结构体本身的大小但可以在运行时额外分配空间。struct DynamicBuffer { int length; char data[]; // 柔性数组成员 }; // 分配空间结构体大小 实际需要的数据大小 struct DynamicBuffer *buf malloc(sizeof(struct DynamicBuffer) 100); buf-length 100; // 现在可以像普通数组一样使用 buf-data[i]柔性数组必须满足至少有一个其他成员。必须是最后一个成员。不能用它声明变量只能通过动态分配。7. 匿名结构体C11在另一个结构体或共用体内部可以定义没有名字的结构体其成员直接作为外层结构体的成员访问无需多加一层。struct Person { char name[20]; struct { // 匿名结构体 int day; int month; int year; }; // 无标签也无成员名 }; struct Person p; p.year 2025; // 直接访问就像 Person 本身有 year 一样这在组织数据时减少了嵌套层次代码更清晰。二、共用体union共用体的声明语法与结构体非常相似关键字换成union。其核心特点是所有成员共享同一块内存同一时刻只有一个成员的值是有效的。1. 基本概念union Data { int i; float f; char str[20]; };对于union Data类型变量它分配的内存大小等于其最大成员的大小考虑对齐。上例中如果float占4字节、char[20]占20字节且对齐值为int的4字节则共用体大小为20字节并可能为了对齐而扩充到20字节或20刚好是对齐值的倍数。给i赋值后再访问f会得到无意义的值因为内存被整型按二进制格式覆盖。2. 访问方式与结构体相同用.和-。union Data d; d.i 10; printf(%d\n, d.i); union Data *pd d; pd-f 3.14f;程序员必须自己跟踪当前生效的是哪个成员否则极易出错。3. 判断字节序经典用法用共用体测试机器是大端序还是小端序。union Endian { unsigned int word; unsigned char bytes[4]; } test {0x01020304}; if (test.bytes[0] 0x04) printf(Little Endian\n); else printf(Big Endian\n);因为所有成员从同一地址开始bytes数组的第一个字节若是最低字节0x04即为小端。4. 类型伪装与数据拆分共用体可用于不安全的类型转换reinterpret_cast 的 C 语言等价或将一个多字节数据拆分为独立字节。union FloatBytes { float f; unsigned char b[4]; }; union FloatBytes fb; fb.f 1.5f; // 查看浮点数在内存中的表示也可以用来实现变体记录一个数据项可能是多种类型之一struct Variant { enum { INT, FLOAT, STR } type; union { int i; float f; char *s; }; // 匿名共用体C11支持 }; struct Variant v; v.type INT; v.i 42;5. 匿名共用体C11与匿名结构体类似匿名共用体的成员将直接提升到外层作用域。struct Register { unsigned int value; union { struct { unsigned int flag0 : 1; unsigned int flag1 : 1; unsigned int mode : 2; }; unsigned char bytes[4]; }; }; // 可以直接访问 flag0, flag1, mode, bytes 等这非常适合硬件寄存器映射可以在整体值和位域表示之间自由切换。6. 大小计算共用体的大小至少是其最大成员的大小同时还要满足所有成员的对齐要求。如果最大成员的大小不是对齐值的整数倍则会被扩充到整数倍。例如union Example { char a[5]; // 5 字节 int b; // 4 字节对齐要求4 }; // 最大成员大小为5对齐值为4最终大小为853填充三、结构体与共用体的对比特性结构体 struct共用体 union内存分配每个成员拥有独立内存空间所有成员共享同一块内存大小等于所有成员大小之和 填充字节等于最大成员大小考虑对齐成员有效性所有成员同时有效同一时刻只有一个成员有效访问安全性安全各自独立需程序员自行管理当前有效成员主要用途打包不同类型的数据组合节省内存、同一内存区的不同解释典型场景记录、节点、数据结构变体记录、字节序检测、类型转换四、综合应用与最佳实践协议解析网络数据包头部通常有固定格式使用结构体位域可以直接映射到内存然后通过指针将接收缓冲区解释为结构体。struct TCPHeader { uint16_t src_port; uint16_t dst_port; uint32_t seq_num; // ... 位域标志等 }; // 注意字节序和对齐通常使用 packed 结构体寄存器映射嵌入式编程中将一个外设的寄存器组定义为一个结构体通过指针映射到绝对地址。#define UART_BASE ((volatile struct UART_Regs *)0x4000C000)变体类型Tagged Union用一个枚举标记当前类型配合匿名共用体实现安全的多类型数据容器。灵活使用typedef和匿名成员适当使用typedef提升代码可读性C11 的匿名结构体/共用体让嵌套组织更简洁。注意可移植性对齐规则、位域存储顺序、大小端在不同编译器和平台上可能不同。跨平台代码应避免依赖具体的二进制布局或明确使用#pragma pack、__attribute__((packed))并结合静态断言_Static_assert检查大小。五、总结结构体是组合数据的利器让相关联的不同类型变量形成一个逻辑整体内存彼此独立可同时存储。共用体体现了“一块内存多种视角”的思想能节省空间、实现底层类型变换但要求程序员自行维护成员有效性。两者都可以通过typedef简化类型名从 C11 开始支持匿名成员进一步增强了数据组织的表达能力。在实际开发中它们经常结合使用结构体描述复杂对象共用体实现同一对象的不同表现形式如变体或底层数据解释从而在保证可读性的同时提供高效的底层控制能力。掌握结构体和共用体是深入理解 C 语言内存模型、编写高效系统级程序的关键一步。