13. 联合体与枚举.md 7.0 KB

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:联合体基础与大小端检测

// 13_union_basics.c
#include <stdio.h>
#include <stddef.h>

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;
}

编译运行:

gcc -Wall -Wextra -std=c99 -o 13_union_basics 13_union_basics.c
./13_union_basics

示例2:联合体用于寄存器位域解析

// 13_union_reg.c
#include <stdio.h>

// 嵌入式常见:寄存器的位域视图与整型视图共享内存
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;
}

编译运行:

gcc -Wall -Wextra -std=c99 -o 13_union_reg 13_union_reg.c
./13_union_reg

示例3:枚举与状态机

// 13_enum_state.c
#include <stdio.h>

// 电梯状态机
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;
}

编译运行:

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 的值可以重复吗?

可以。不同枚举成员可以有相同值,编译器不报错。但同一枚举类型中重复值可能导致逻辑错误。