Skip to content

C 联合体

一个 union 是一种用户定义的数据类型,类似于 struct,但有一个关键区别:联合体中的所有成员共享 同一个内存位置。这意味着联合体变量在任何给定时间只能存储其 一个 成员的值。

使用联合体通常是因为需要在同一个内存空间中存储不同类型的数据,而通常在某个特定时刻只需要其中一种类型。与结构体相比,这可以节省内存,因为结构体中每个成员都有自己的独立空间。

定义联合体的语法与定义结构体非常相似,使用 union 关键字。

union [union_tag] {
type member1_name;
type member2_name;
// ... other members
// ... 其他成员
} [optional_variable_declarations];
// [可选的变量声明]

示例:一个用于存储整数、浮点数或字符数组的联合体。

// Define the union named 'DataValue'
// 定义名为 'DataValue' 的联合体
union DataValue {
int i; // Integer value
// 整数值
float f; // Floating-point value
// 浮点数值
char str[20]; // String value (character array)
// 字符串值 (字符数组)
};

类型为 union DataValue 的变量可以存储一个 int,或者一个 float,或者一个 char[20],但不能同时存储。

联合体变量的大小由其 最大成员 的大小决定。编译器分配足够的内存来容纳最大的成员,外加为对齐可能需要的任何填充。

#include <stdio.h>
#include <string.h> // Included for potential string operations later
// 包含此头文件用于后续潜在的字符串操作
union DataValue {
int i; // Typically 4 bytes
// 通常 4 字节
float f; // Typically 4 bytes
// 通常 4 字节
char str[20]; // 20 bytes
// 20 字节
double d; // Typically 8 bytes
// 通常 8 字节
};
int main(void) {
union DataValue data;
// The size will be at least sizeof(char[20]) = 20 bytes,
// possibly more due to alignment requirements for 'double'.
// 大小将至少是 sizeof(char[20]) = 20 字节,
// 由于 'double' 的对齐要求,可能会更大。
printf("Memory size occupied by union DataValue: %zu bytes\n", sizeof(data));
printf("Size of int member: %zu bytes\n", sizeof(data.i));
printf("Size of float member: %zu bytes\n", sizeof(data.f));
printf("Size of string member: %zu bytes\n", sizeof(data.str));
printf("Size of double member: %zu bytes\n", sizeof(data.d));
return 0;
}

示例输出(在需要 8 字节对齐的系统上):

Memory size occupied by union DataValue: 24 bytes
Size of int member: 4 bytes
Size of float member: 4 bytes
Size of string member: 20 bytes
Size of double member: 8 bytes

注意大小是 24 字节,而不是 20 字节,因为 double 成员可能需要 8 字节对齐,迫使总大小成为 8 的倍数。

您使用与结构体相同的成员访问运算符(.)来访问联合体成员。

语法:union_variable.member_name

关键规则: 您应该只读取最近写入的那个成员。读取不同的成员将导致访问上次写入值的原始字节,并根据您读取的类型重新解释这些字节,这通常会导致垃圾值或未定义行为。

示例:正确和不正确的使用方式

#include <stdio.h>
#include <string.h>
union DataValue {
int i;
float f;
char str[20];
};
int main(void) {
union DataValue data;
// --- Scenario 1: Writing and reading the same member (CORRECT) ---
// --- 场景 1:写入和读取同一成员(正确) ---
printf("--- Correct Usage ---\n");
printf("--- 正确使用 ---\n");
data.i = 10;
printf("data.i: %d (after writing to i)\n", data.i); // OK
// 正常
data.f = 220.5f;
printf("data.f: %.1f (after writing to f)\n", data.f); // OK
// 正常
strncpy(data.str, "C Programming", sizeof(data.str) - 1);
data.str[sizeof(data.str) - 1] = '\0';
printf("data.str: %s (after writing to str)\n", data.str); // OK
// 正常
// --- Scenario 2: Writing one member, reading another (INCORRECT) ---
// --- 场景 2:写入一个成员,读取另一个成员(不正确) ---
printf("\n--- Incorrect Usage ---\n");
printf("\n--- 不正确使用 ---\n");
data.i = 1094795585; // An integer value (binary representation matters)
// 一个整数值(二进制表示很重要)
// Now try reading data.f and data.str - they will contain garbage
// 现在尝试读取 data.f 和 data.str - 它们将包含垃圾值
// because the memory holds the bits for the integer 1094795585.
// 因为内存中存储的是整数 1094795585 的二进制位。
printf("data.i: %d (last write was to i)\n", data.i);
// data.i: %d (最后写入的是 i)
printf("data.f: %f (reading f after writing i - GARBAGE!)\n", data.f);
// data.f: %f (写入 i 后读取 f - 垃圾值!)
printf("data.str: %s (reading str after writing i - GARBAGE!)\n", data.str);
// data.str: %s (写入 i 后读取 str - 垃圾值!)
// Example: Write float, read int
// 示例:写入 float,读取 int
data.f = 99.9f;
printf("\ndata.f: %f (last write was to f)\n", data.f);
// data.f: %f (最后写入的是 f)
printf("data.i: %d (reading i after writing f - GARBAGE!)\n", data.i);
// data.i: %d (写入 f 后读取 i - 垃圾值!)
return 0;
}

示例输出(垃圾值会变化):

--- Correct Usage ---
data.i: 10 (after writing to i)
data.f: 220.5 (after writing to f)
data.str: C Programming (after writing to str)
--- Incorrect Usage ---
data.i: 1094795585 (last write was to i)
data.f: 10.000000 (reading f after writing i - GARBAGE!)
data.str: C (reading str after writing i - GARBAGE!)
data.f: 99.900002 (last write was to f)
data.i: 1120403458 (reading i after writing f - GARBAGE!)

由于您需要知道当前哪个成员是有效的,联合体通常与一个“标签”成员(通常是 enum 或 int)一起使用在一个结构体中,该标签成员指示当前存储值的类型。

#include <stdio.h>
#include <string.h>
// Enum to identify the type stored in the union
// 枚举类型,用于标识联合体中存储的数据类型
typedef enum {
TYPE_INT,
TYPE_FLOAT,
TYPE_STRING
} ValueType;
// The union itself
// 联合体本身
typedef union {
int i;
float f;
char str[50];
} ValueUnion;
// Structure containing the tag and the union (Tagged Union)
// 包含标签和联合体的结构体(带标签的联合体)
typedef struct {
ValueType type; // Tag indicating which member is active
// 标签,指示哪个成员是活动的
ValueUnion value; // The union holding the actual data
// 联合体, holding 实际数据
} TaggedValue;
// Function to print the tagged value correctly
// 正确打印带标签的值的函数
void print_tagged_value(TaggedValue tv) {
printf("Type: ");
// 类型:
switch (tv.type) {
case TYPE_INT:
printf("Integer, Value: %d\n", tv.value.i);
// 整数,值:%d
break;
case TYPE_FLOAT:
printf("Float, Value: %.2f\n", tv.value.f);
// 浮点数,值:%.2f
break;
case TYPE_STRING:
printf("String, Value: \"%s\"\n", tv.value.str);
// 字符串,值:"%s"
break;
default:
printf("Unknown\n");
// 未知
break;
}
}
int main(void) {
TaggedValue val1, val2, val3;
// Store and print an integer
// 存储并打印一个整数
val1.type = TYPE_INT;
val1.value.i = 123;
print_tagged_value(val1);
// Store and print a float
// 存储并打印一个浮点数
val2.type = TYPE_FLOAT;
val2.value.f = 45.67f;
print_tagged_value(val2);
// Store and print a string
// 存储并打印一个字符串
val3.type = TYPE_STRING;
strncpy(val3.value.str, "Hello Union!", sizeof(val3.value.str) - 1);
val3.value.str[sizeof(val3.value.str) - 1] = '\0';
print_tagged_value(val3);
return 0;
}

输出:

Type: Integer, Value: 123
Type: Float, Value: 45.67
Type: String, Value: "Hello Union!"

带标签的联合体是一种安全且常见的方式,用于处理在不同时间可以持有不同类型数据的变量,确保您总是正确地解释存储的值。