# 13 联合体与枚举 ## 13.1 完整概念讲解 ### 联合体(union) 联合体的所有成员共享同一块内存空间,同一时刻只有一个成员有效。联合体的大小等于最大成员的大小(含对齐填充)。 ### 枚举(enum) 枚举用于定义一组命名的整型常量,使代码更具可读性。枚举值从 0 开始递增,也可以手动指定值。 ## 13.2 核心API/语法 | 语法 | 说明 | | ----------------------------- | ---------------- | | `union Tag { ... };` | 联合体声明 | | `union Tag u = {.x = 1};` | C99 指定初始化器 | | `u.member` | 访问当前有效成员 | | `enum Tag { A, B, C };` | 枚举声明 | | `enum Tag val = A;` | 枚举赋值 | | `enum Tag { A=10, B, C=20 };` | 手动指定枚举值 | ## 13.3 代码示例(完整可编译,附gcc命令) ### 示例1:联合体基础与大小端检测 ```c // 13_union_basics.c #include #include union Data { int i; float f; char str[4]; }; // 大小端检测联合体 union EndianCheck { unsigned int val; unsigned char bytes[4]; }; int main(void) { // 所有成员共享同一块内存 union Data d; d.i = 0x41424344; printf("d.i = 0x%X\n", d.i); printf("d.f = %f\n", d.f); // 读取浮点(结果无意义) printf("d.str = %.4s\n", d.str); // 读取字符串 printf("\nsizeof(union Data) = %zu\n", sizeof(union Data)); printf("sizeof(int) = %zu, sizeof(float) = %zu\n", sizeof(int), sizeof(float)); // 大小端检测 union EndianCheck ec; ec.val = 0x01020304; printf("\nEndian check for 0x01020304:\n"); printf(" bytes[0]=0x%02X, bytes[1]=0x%02X, bytes[2]=0x%02X, bytes[3]=0x%02X\n", ec.bytes[0], ec.bytes[1], ec.bytes[2], ec.bytes[3]); if (ec.bytes[0] == 0x01) { printf(" -> Big Endian\n"); } else if (ec.bytes[0] == 0x04) { printf(" -> Little Endian\n"); } else { printf(" -> Middle Endian (PDP)\n"); } return 0; } ``` **编译运行:** ```bash gcc -Wall -Wextra -std=c99 -o 13_union_basics 13_union_basics.c ./13_union_basics ``` ### 示例2:联合体用于寄存器位域解析 ```c // 13_union_reg.c #include // 嵌入式常见:寄存器的位域视图与整型视图共享内存 union Reg { unsigned int raw; struct { unsigned int enable : 1; // bit 0 unsigned int mode : 3; // bit 1-3 unsigned int irq_en : 1; // bit 4 unsigned int reserved: 27; // bit 5-31 } bits; }; int main(void) { union Reg reg; reg.raw = 0; // 通过位域设置 reg.bits.enable = 1; reg.bits.mode = 5; // 101b reg.bits.irq_en = 1; printf("reg.raw = 0x%08X\n", reg.raw); printf(" enable = %u\n", reg.bits.enable); printf(" mode = %u\n", reg.bits.mode); printf(" irq_en = %u\n", reg.bits.irq_en); // 也可以直接写整型 reg.raw = 0x0000001B; printf("\nAfter raw write 0x1B:\n"); printf(" enable = %u\n", reg.bits.enable); printf(" mode = %u\n", reg.bits.mode); printf(" irq_en = %u\n", reg.bits.irq_en); return 0; } ``` **编译运行:** ```bash gcc -Wall -Wextra -std=c99 -o 13_union_reg 13_union_reg.c ./13_union_reg ``` ### 示例3:枚举与状态机 ```c // 13_enum_state.c #include // 电梯状态机 enum ElevatorState { STATE_IDLE = 0, STATE_MOVING_UP = 1, STATE_MOVING_DOWN= 2, STATE_DOOR_OPEN = 3, STATE_ERROR = 4 }; // 事件 enum ElevatorEvent { EVT_CALL_UP, EVT_CALL_DOWN, EVT_ARRIVE_FLOOR, EVT_OPEN_DOOR, EVT_CLOSE_DOOR, EVT_FAULT }; const char *state_name(enum ElevatorState s) { switch (s) { case STATE_IDLE: return "IDLE"; case STATE_MOVING_UP: return "MOVING_UP"; case STATE_MOVING_DOWN: return "MOVING_DOWN"; case STATE_DOOR_OPEN: return "DOOR_OPEN"; case STATE_ERROR: return "ERROR"; default: return "UNKNOWN"; } } enum ElevatorState next_state(enum ElevatorState current, enum ElevatorEvent evt) { switch (current) { case STATE_IDLE: if (evt == EVT_CALL_UP) return STATE_MOVING_UP; if (evt == EVT_CALL_DOWN) return STATE_MOVING_DOWN; if (evt == EVT_FAULT) return STATE_ERROR; break; case STATE_MOVING_UP: case STATE_MOVING_DOWN: if (evt == EVT_ARRIVE_FLOOR) return STATE_DOOR_OPEN; if (evt == EVT_FAULT) return STATE_ERROR; break; case STATE_DOOR_OPEN: if (evt == EVT_CLOSE_DOOR) return STATE_IDLE; break; case STATE_ERROR: break; } return current; } int main(void) { enum ElevatorState state = STATE_IDLE; enum ElevatorEvent events[] = { EVT_CALL_UP, EVT_ARRIVE_FLOOR, EVT_CLOSE_DOOR, EVT_CALL_DOWN, EVT_FAULT }; int n = sizeof(events) / sizeof(events[0]); printf("Start: %s\n", state_name(state)); for (int i = 0; i < n; i++) { state = next_state(state, events[i]); printf(" Event %d -> State: %s\n", i + 1, state_name(state)); } return 0; } ``` **编译运行:** ```bash gcc -Wall -Wextra -std=c99 -o 13_enum_state 13_enum_state.c ./13_enum_state ``` ## 13.4 注意事项与易错点 1. **union 同时只读一个成员**:写入 `A` 后读取 `B` 是未定义行为(类型双关除外,如通过 `unsigned char*` 逐字节读取)。 2. **union 大小含对齐填充**:`sizeof(union)` 是最大成员大小向上对齐到最大对齐值的结果。 3. **enum 本质是 int**:C 语言不检查 enum 值的范围,可以赋任意整数给 enum 变量。 4. **enum 的值是编译期常量**:不能用 `scanf` 读取 enum(要读 int 再赋值)。 5. **命名冲突**:enum/union 的标签名在同一作用域不能与变量名冲突。 ## 13.5 面试要点(5个Q&A) **Q1:union 和 struct 的区别?** > struct 所有成员各自占用独立内存;union 所有成员共享同一块内存,大小等于最大成员。 **Q2:如何用 union 检测大小端?** > 将整数写入 union 的 `unsigned int` 成员,再通过 `unsigned char` 数组读取最低字节。最低地址存 `0x01` 是大端,存 `0x04` 是小端。 **Q3:enum 和 #define 有什么区别?** > 1. enum 是编译期类型,调试器可以打印名称;#define 是预处理文本替换。 > 2. enum 自动编号,不需要手动维护值。 > 3. enum 有作用域限制(在函数内声明只在函数内可见),#define 从定义点到文件末尾。 **Q4:union 在嵌入式中有什么用途?** > 解析寄存器:同一地址既可按位域读取各标志位,也可按整型批量读写。也用于类型双关(type punning)在协议解析中。 **Q5:C 语言中 enum 的值可以重复吗?** > 可以。不同枚举成员可以有相同值,编译器不报错。但同一枚举类型中重复值可能导致逻辑错误。