共用体(union)跟结构体长得几乎一模一样,唯一致命的不同在内存:结构体的成员各自占一块内存,共用体的所有成员共享同一块内存。这块共享内存的大小,等于其中最大的那个成员。

一句话记住共用体

结构体是一堆变量住各自房间;共用体是一堆名字指向同一个房间。同一个内存位置,你想按 int 读就按 int 读,想按字符数组读就按字符数组读。

C 语言共用体基础用法示例运行
#include <stdio.h>

union Student {
    int rollNo;
    float height;
    char firstLetter;
};

int main() {
    union Student data;

    data.rollNo = 21;
    printf("%d\n", data.rollNo);     // 21

    data.height = 5.2;
    printf("%.2f\n", data.height);   // 5.20,此时 rollNo 已被覆盖!

    data.firstLetter = 'N';
    printf("%c\n", data.firstLetter); // N
    return 0;
}

关键行为:给某个成员赋值,会覆盖掉其他成员的值。同一个时刻只有一个成员有意义。访问语法和结构体一样用 .

共享内存 大小 = 最大成员(4 字节) rollNo (int) height (float) firstLetter (char) 给一个成员赋值,会覆盖其他成员的值
结构体是成员各住各的房间;共用体是一堆名字指向同一个房间,同一时刻只有一个有意义。

共用体的大小

共用体的内存 = 最大成员的大小(可能再被编译器补点对齐填充):

union A {
    int x;      /* 4 字节 */
    char y;     /* 1 字节 */
};

union B {
    int arr[10];  /* 40 字节 */
    char y;       /* 1 字节 */
};

printf("Sizeof A: %zu\n", sizeof(union A));   // 4
printf("Sizeof B: %zu\n", sizeof(union B));   // 40


小成员能安全地存在大成员的空间里不会溢出,因为空间本来就按最大的划好了。

典型玩法:把整数拆成字节看

想让 int 的每个字节都能单独读,用指针转来转去很麻烦,共用体直接给你命名好的通道:

C 语言共用体内存共享示例运行
union intParts {
    int theInt;
    char bytes[sizeof(int)];   /* 4 个字符,和一个 int 共享内存 */
};

union intParts parts;
parts.theInt = 5968145;        /* 一个大于 255 的整数 */

printf("整数是 %d\n", parts.theInt);
printf("四个字节分别是 [%d, %d, %d, %d]\n",
       parts.bytes[0], parts.bytes[1],
       parts.bytes[2], parts.bytes[3]);

对应地,如果不用共用体就得写一坨指针转换:*((char*)&theInt + 0)……共用体版清晰得多。

省内存:带类型标签的联合

假设要一个数值结构,可能是整数也可能是浮点。不用共用体会这样浪费:

struct operator {
    int intNum;
    float floatNum;
    int type;
    double doubleNum;   /* 四个都住各自房间,占一大块内存 */
};

程序里这种结构多了,内存哗哗地走。改用共用体,只占"int + 最大成员":

struct operator {
    int type;                      /* 记录现在存的是哪种类型 */
    union {
        int intNum;
        float floatNum;
        double doubleNum;
    } types;                       /* 三种类型共享一块内存 */
};

使用时先定 type 再塞对应成员:

struct operator op;
op.type = 0;            /* 0 表示存整数,真实代码里会用枚举或宏 */
op.types.intNum = 352;

如果不给 union 起名字(匿名共用体),成员可以直接挂到外层结构体上访问:

struct operator {
    int type;
    union {
        int intNum;
        float floatNum;
        double doubleNum;
    };                  /* 没起名 */
};

op.type = 0;
op.intNum = 352;        /* 直接访问,不用绕 types.intNum */

最常见的用法:名字和下标互相换

有时候想用可读的名字,又想能按下标循环访问。把命名的成员和一个数组塞进同一个共用体:

union Coins {
    struct {
        int quarter;
        int dime;
        int nickel;
        int penny;
    };                    /* 匿名结构体,成员直接挂在 union 上 */
    int coins[4];         /* 和上面四个成员共享同一块内存 */
};

union Coins change;
for (int i = 0; i < 4; i++) {
    change.coins[i] = i * 10;   /* 用下标填 */
}
printf("quarter=%d dime=%d nickel=%d penny=%d\n",
       change.quarter, change.dime,
       change.nickel, change.penny);   /* 用名字读 */

coins[i] 恰好对应 quarter/dime/nickel/penny,因为它们在内存里按声明顺序排。

共用体的适用场合

  • 省内存:多个类型共享一片空间。
  • 硬件映射:同一个寄存器,既能当状态位看又能当命令字看。
  • 单类型场景:数据是 A 或 B 但不能同时,比如一个消息要么是 ID 要么是字符串。

结构体浪费内存但安全,共用体省内存但要你自己盯住现在存的是哪个。