第24篇 联合体与枚举

目录

一、自定义类型:联合体与枚举的底层原理

[1.1 联合体类型:共享内存的底层机制](#1.1 联合体类型:共享内存的底层机制)

[1.2 联合体大小的计算规则](#1.2 联合体大小的计算规则)

[1.3 联合体的典型应用:大小端判断](#1.3 联合体的典型应用:大小端判断)

二、枚举类型:类型安全的符号常量

[2.1 枚举的声明与默认值](#2.1 枚举的声明与默认值)

[2.2 枚举的显式赋值](#2.2 枚举的显式赋值)

[2.3 枚举的优势与类型检查](#2.3 枚举的优势与类型检查)

三、全章节逻辑闭环总结


一、自定义类型:联合体与枚举的底层原理

1.1 联合体类型:共享内存的底层机制

联合体(Union),也称为共用体,是一种特殊的自定义类型。它允许在同一块内存空间中存储不同的数据类型,但同一时刻只能存储其中一个成员的值。这与结构体(Struct)为每个成员分配独立内存空间的机制形成鲜明对比。

1.1.1 联合体的声明与内存布局 联合体的声明语法与结构体类似,但其内存模型是核心区别。编译器只为联合体分配足以容纳其最大成员的内存空间。

cpp 复制代码
#include <stdio.h>

union Un
{
    char c;
    int i;
};

int main(void)
{
    union Un un = {0};
    // 输出联合体大小
    printf("Size of union Un: %zu\n", sizeof(un));
    return 0;
}

运行分析 : 上述代码的输出结果为 4。这是因为 union Un 包含一个 char (1字节) 和一个 int (通常为4字节)。为了能够存储最大的成员 int i,编译器为该联合体分配了4字节的内存空间。

硬件视角的内存共享 从硬件角度看,联合体的所有成员都映射到同一段物理地址上。我们可以通过打印地址来验证这一点。

cpp 复制代码
#include <stdio.h>

union Un
{
    char c;
    int i;
};

int main(void)
{
    union Un un = {0};
    // 打印各成员及联合体变量的地址
    printf("Address of un.i: %p\n", (void*)&(un.i));
    printf("Address of un.c: %p\n", (void*)&(un.c));
    printf("Address of un:   %p\n", (void*)&un);
    return 0;
}

运行分析 : 三个 printf 语句输出的地址是完全相同 的。这从底层证实了 un.iun.cun 本身都指向内存中的同一个起始位置。对其中一个成员的写操作,会直接覆盖其他成员的数据。

1.2 联合体大小的计算规则

联合体的大小计算遵循两条核心规则:

  1. 基础大小:联合体的大小至少是其最大成员的大小。
  2. 对齐规则 :如果最大成员的大小不是其最大对齐数的整数倍,则需要向上对齐到最大对齐数的整数倍。

1.2.1 大小计算实战 让我们分析以下两个联合体的大小(假设默认对齐数为8):

cpp 复制代码
#include <stdio.h>

union Un1
{
    char c[5]; // 5字节,对齐数为1
    int i;     // 4字节,对齐数为4
};

union Un2
{
    short c[7]; // 14字节,对齐数为2
    int i;      // 4字节,对齐数为4
};

int main(void)
{
    printf("Size of Un1: %zu\n", sizeof(union Un1));
    printf("Size of Un2: %zu\n", sizeof(union Un2));
    return 0;
}

运行分析

  • union Un1 : 最大成员是 char c[5],大小为5字节。所有成员中最大的对齐数是 int 的对齐数4。5不是4的整数倍,因此需要向上对齐到4的倍数,即8。所以 sizeof(union Un1)8
  • union Un2 : 最大成员是 short c[7],大小为14字节。所有成员中最大的对齐数是 int 的对齐数4。14不是4的整数倍,因此需要向上对齐到4的倍数,即16。所以 sizeof(union Un2)16
1.3 联合体的典型应用:大小端判断

联合体的内存共享特性使其成为判断系统字节序(Endianness)的理想工具。

1.3.1 原理推导

  • 小端模式 (Little-Endian):数据的低位字节存储在低地址。
  • 大端模式 (Big-Endian):数据的高位字节存储在低地址。

通过联合体,我们可以用一个 int 成员写入数据,再用 char 成员读取最低地址的那个字节,从而判断字节序。

cpp 复制代码
#include <stdio.h>

int check_sys(void)
{
    union
    {
        int i;
        char c;
    } un;
    un.i = 1;
    // 如果c读取到1,说明低地址存储的是低位字节,为小端
    // 如果c读取到0,说明低地址存储的是高位字节,为大端
    return un.c;
}

int main(void)
{
    if (check_sys() == 1)
    {
        printf("Little-Endian\n");
    }
    else
    {
        printf("Big-Endian\n");
    }
    return 0;
}

运行分析 : 当 un.i = 1 时,其十六进制表示为 0x00000001

  • 小端 机器上,内存布局为 01 00 00 00 (从低地址到高地址)。un.c 读取低地址的字节,得到 0x01 (即1)。
  • 大端 机器上,内存布局为 00 00 00 01un.c 读取低地址的字节,得到 0x00 (即0)。

二、枚举类型:类型安全的符号常量

2.1 枚举的声明与默认值

枚举(Enumeration)用于将可能的取值一一列举出来,使代码更具可读性和可维护性。

2.1.1 基础声明语法

cpp 复制代码
#include <stdio.h>

enum Day
{
    Mon,    // 默认值为 0
    Tues,   // 默认值为 1
    Wed,    // 默认值为 2
    Thur,   // 默认值为 3
    Fri,    // 默认值为 4
    Sat,    // 默认值为 5
    Sun     // 默认值为 6
};

int main(void)
{
    enum Day today = Wed;
    printf("Today is day: %d\n", today); // 输出 2
    return 0;
}

运行分析enum Day 定义了一组具名整型常量。默认情况下,第一个枚举常量的值为0,后续每个常量的值依次递增1。

2.2 枚举的显式赋值

可以在声明时为枚举常量指定初始值。

cpp 复制代码
#include <stdio.h>

enum Color
{
    RED = 2,
    GREEN = 4,
    BLUE = 8
};

int main(void)
{
    enum Color my_color = GREEN;
    printf("Color value: %d\n", my_color); // 输出 4
    return 0;
}

运行分析 : 通过显式赋值,REDGREENBLUE 的值分别被设定为2、4、8,而不是默认的0、1、2。

2.3 枚举的优势与类型检查

相比于使用 #define 宏定义常量,枚举具有显著优势,尤其是在类型安全方面。

2.3.1 枚举 vs #define

特性 enum (枚举) #define (宏)
类型检查 。编译器会进行类型检查,更严谨。 。预处理阶段进行文本替换,无类型概念。
调试支持 支持。调试器可以识别枚举变量和常量。 不支持。预处理后符号被替换,调试困难。
作用域 遵循作用域规则 全局有效,从定义处到文件末尾。
可维护性 。可以一次性定义一组相关常量。 。需要为每个常量单独定义。

2.3.2 C语言中的隐式转换 在C语言中,枚举类型和整型之间的转换相对宽松。

cpp 复制代码
#include <stdio.h>

enum Color
{
    RED = 1,
    GREEN = 2,
    BLUE = 4
};

int main(void)
{
    enum Color clr = GREEN;
    printf("clr = %d\n", clr); // 输出 2

    // C语言允许直接用整数给枚举变量赋值
    clr = 100;
    printf("clr = %d\n", clr); // 输出 100

    return 0;
}

运行分析 : 尽管 100 并不是 enum Color 中定义的任何一个合法值,C编译器通常只会给出警告而非错误,并允许赋值。这体现了C语言在类型检查上的灵活性,但也要求程序员自行保证逻辑的正确性。在C++等语言中,这种赋值是严格禁止的。

三、全章节逻辑闭环总结

本章深入探讨了C语言中两种重要的自定义类型:联合体与枚举,并从底层原理到应用实践进行了完整推导。

  1. 联合体 (Union)

    • 核心理论 :所有成员共享同一块内存空间,其大小由最大成员决定,并遵循内存对齐规则。
    • 底层机制:通过地址验证,确认了成员变量的地址与联合体变量地址完全一致,这是实现数据复用的基础。
    • 典型应用 :利用其内存共享特性,可以高效地判断系统的大小端字节序
    • 内存优化:在需要存储多种类型但不同时使用的场景,使用联合体可以显著节省内存空间。
  2. 枚举 (Enum)

    • 核心理论:用于定义一组具名的整型常量,增强了代码的可读性和可维护性。
    • 优势对比 :相较于 #define 宏,枚举提供了类型检查和更好的调试支持,是定义符号常量的更优选择。
    • 使用注意:在C语言中,枚举变量可以被赋予任何整数值,缺乏严格的类型约束,编程时需注意。