C语言union联合体的用法(非常详细)
在 C语言中,联合体(union)是一种特殊的数据类型,它允许在相同的内存位置存储不同类型的数据。联合体的大小等于其最大成员的大小。使用联合体时,同一时刻只能访问其中一个成员,因为其他成员的数据会被覆盖。
联合体常用于节省内存空间,特别是在嵌入式系统或处理大量数据时。下面是一个简单的联合体示例:
联合体的语法非常类似于结构体的语法,几乎仅仅换了一个关键字而已。让我们来看看它们之间的差别。我们先使用 sizeof 分别测试它们的大小,具体代码如下:
对于结构体来说,char 占用 1 字节,short 占用 2 字节,long long 占用 8 字节。它们如果相邻紧密排列,则在逻辑上将占用 11 字节。这两个结果似乎都有些奇怪,让我们输出它们成员的地址,详细地分析它们的内存分布情况,具体代码如下:

图 1 结构体的内存分布
我们注意到在 char 和 short 类型之间留出了 1 字节的空间,而在 short 和 long long 类型之间留出了 4 字节的空间。
这种现象被称为内存对齐。虽然这样做会浪费一些内存空间,但是对齐后的数据能够更快地被访问。因此,内存对齐可以提高数据访问速度。
内存对齐(memory alignment)是一种处理器、操作系统和编译器共同实现的优化策略,旨在提高数据访问速度。它要求数据在内存中以其自然边界对齐。具体来说,对于大小为 N 字节的数据类型,它的内存地址应该是 N 的整数倍。这是因为处理器在访问特定边界对齐的内存时,通常能更高效地读取和写入数据。内存对齐只需简单理解即可,无须深入探讨和研究。
下面我们来看看联合体中成员的内存分布情况。联合体 u 的成员 c、s 和 ll 在内存中的首地址都为 8649896,如下图所示:

图 2 首地址重叠
我们可以看到,联合体中不同成员的首地址是重叠的。
首先,程序将 123 赋值给 u.c,然后输出它。输出将显示 u.c = 123。
接着,程序将 0 赋值给 u.s。由于联合体成员共享同一内存空间,这将覆盖 u.c 的值。然后,程序再次输出 u.c 的值,输出将显示 u.c = 0。这是因为赋值给 u.s 的操作已经改变了共享的内存内容。程序执行结果为:
如果我们用结构体 struct 来存储这种信息,那么结构体中就需要准备三个不同类型的成员。由于一次只会出现一种形态,因此每次仅用一个成员,其他两个成员便会留空。另外,还需要一个整型的 type 成员来标记这一次是什么类型。
例如:1 代表整型,2 代表浮点,3 代表字符串。根据上述的内容,我们可以写出如下的结构体:
接下来,我们可以定义一个函数 printMsg(),根据消息的 type 使用不同的方式处理消息。
完整的代码如下:
printMsg() 函数接收一个 message 类型的参数,根据 type 成员的值来输出不同类型的数据。如果 type 为 1,则输出整数;如果为 2,则输出浮点数;如果为 3,则输出字符串。
在 main() 函数中,我们创建了一个 message 类型的数组 msg,包含三个元素,并且为这三个元素分别赋值。第一个元素表示一个整数,所以将 type 设为 1。第二个元素表示一个浮点数,所以将 type 设为 2。第三个元素表示一个字符串,所以将 type 设为 3。
然后,我们使用一个 for 循环遍历数组,调用 printMsg() 函数输出每个元素的值。
运行结果如下,该程序成功输出了对应的值。
这种改进能够节省内存空间,因为使用联合体只会为 int n、float f和char *str 中最大的成员分配空间。联合体成员共享内存,因此整个结构体占用的空间会减少。
另外,还有一种匿名嵌套的写法。嵌套的 union 中没必要写明成员名 u。在其后的使用中,union 中的成员被当作 message 的成员一样处理。
联合体常用于节省内存空间,特别是在嵌入式系统或处理大量数据时。下面是一个简单的联合体示例:
union { char c; short s; long long ll; }u;
联合体的语法非常类似于结构体的语法,几乎仅仅换了一个关键字而已。让我们来看看它们之间的差别。我们先使用 sizeof 分别测试它们的大小,具体代码如下:
#include <stdio.h> int main() { struct { char c; short s; long long ll; } s; union { char c; short s; long long ll; } u; printf("sizeof s %d\n", sizeof(s)); printf("sizeof u %d\n", sizeof(u)); return 0; }运行结果如下:
sizeof s 16
sizeof u 8
对于结构体来说,char 占用 1 字节,short 占用 2 字节,long long 占用 8 字节。它们如果相邻紧密排列,则在逻辑上将占用 11 字节。这两个结果似乎都有些奇怪,让我们输出它们成员的地址,详细地分析它们的内存分布情况,具体代码如下:
#include <stdio.h> int main() { struct { char c; short s; long long ll; } s; union { char c; short s; long long ll; } u; printf("&s.c %d \n", &s.c); printf("&s.s %d \n", &s.s); printf("&s.ll %d \n\n", &s.ll); printf("&u.c %d \n", &u.c); printf("&u.s %d \n", &u.s); printf("&u.ll %d \n", &u.ll); return 0; }运行结果为:
&s.c 8649904
&s.s 8649906
&s.ll 8649912
&u.c 8649896
&u.s 8649896
&u.ll 8649896

图 1 结构体的内存分布
我们注意到在 char 和 short 类型之间留出了 1 字节的空间,而在 short 和 long long 类型之间留出了 4 字节的空间。
这种现象被称为内存对齐。虽然这样做会浪费一些内存空间,但是对齐后的数据能够更快地被访问。因此,内存对齐可以提高数据访问速度。
内存对齐(memory alignment)是一种处理器、操作系统和编译器共同实现的优化策略,旨在提高数据访问速度。它要求数据在内存中以其自然边界对齐。具体来说,对于大小为 N 字节的数据类型,它的内存地址应该是 N 的整数倍。这是因为处理器在访问特定边界对齐的内存时,通常能更高效地读取和写入数据。内存对齐只需简单理解即可,无须深入探讨和研究。
下面我们来看看联合体中成员的内存分布情况。联合体 u 的成员 c、s 和 ll 在内存中的首地址都为 8649896,如下图所示:

图 2 首地址重叠
我们可以看到,联合体中不同成员的首地址是重叠的。
C语言联合体的性质
由于联合体中的各成员之间存在重叠的部分,存储一个成员后,将覆盖其他成员的数据。下面的程序展示了一个使用联合体的示例。#include <stdio.h> int main() { union { char c; short s; long long ll; } u; u.c = 123; printf("u.c = %d\n", u.c); u.s = 0; printf("u.c = %d\n", u.c); return 0; }这段代码定义了一个联合体 u,包含了三个成员:char c、short s 和 long long ll。接下来,程序执行以下操作:
首先,程序将 123 赋值给 u.c,然后输出它。输出将显示 u.c = 123。
接着,程序将 0 赋值给 u.s。由于联合体成员共享同一内存空间,这将覆盖 u.c 的值。然后,程序再次输出 u.c 的值,输出将显示 u.c = 0。这是因为赋值给 u.s 的操作已经改变了共享的内存内容。程序执行结果为:
u.c = 123
u.c = 0
C语言联合体的应用
下面是一个使用联合体的应用示例。假设有一种信息它只有三种形态,即整数、浮点数和字符串,并且一次只能出现一种形态。如果我们用结构体 struct 来存储这种信息,那么结构体中就需要准备三个不同类型的成员。由于一次只会出现一种形态,因此每次仅用一个成员,其他两个成员便会留空。另外,还需要一个整型的 type 成员来标记这一次是什么类型。
例如:1 代表整型,2 代表浮点,3 代表字符串。根据上述的内容,我们可以写出如下的结构体:
struct message { int type; int n; float f; char *str; };
接下来,我们可以定义一个函数 printMsg(),根据消息的 type 使用不同的方式处理消息。
void printMsg(struct message msg) { switch (msg.type) { case 1: printf("%d\n", msg.n); break; case 2: printf("%f\n", msg.f); break; case 3: printf("%s\n", msg.str); break; } }
完整的代码如下:
#include <stdio.h> struct message { int type; int n; float f; char *str; }; void printMsg(struct message msg) { switch (msg.type) { case 1: printf("%d\n", msg.n); break; case 2: printf("%f\n", msg.f); break; case 3: printf("%s\n", msg.str); break; } } int main() { struct message msg[3]; // 第一条信息为整型,type 为 1 msg[0].type = 1; msg[0].n = 123; // 第二条信息为浮点型,type 为 2 msg[1].type = 2; msg[1].f = 3.1415926; // 第三条信息为字符串,type 为 3 msg[2].type = 3; msg[2].str = "HelloWorld"; for (int i = 0; i < 3; i++) { printMsg(msg[i]); } return 0; }这段代码定义了一个名为 message 的结构体,用于存储不同类型的数据(整数、浮点数和字符串)。
printMsg() 函数接收一个 message 类型的参数,根据 type 成员的值来输出不同类型的数据。如果 type 为 1,则输出整数;如果为 2,则输出浮点数;如果为 3,则输出字符串。
在 main() 函数中,我们创建了一个 message 类型的数组 msg,包含三个元素,并且为这三个元素分别赋值。第一个元素表示一个整数,所以将 type 设为 1。第二个元素表示一个浮点数,所以将 type 设为 2。第三个元素表示一个字符串,所以将 type 设为 3。
然后,我们使用一个 for 循环遍历数组,调用 printMsg() 函数输出每个元素的值。
运行结果如下,该程序成功输出了对应的值。
123
3.141593
HelloWorld
struct message { int type; union { int n; float f; char *str; } u; };当然,type 成员必须有,否则无法判断是什么类型的信息。收到消息后,程序还需要根据消息的 type 使用不同的方式进行处理。确定 type 后,程序从 msg 中找到联合体成员 u,再根据类型,并选择对应的成员进行处理。完整的代码如下:
#include <stdio.h> struct message { int type; union { int n; float f; char* str; } u; }; void printMsg(struct message msg) { switch (msg.type) { case 1: printf("%d\n", msg.u.n); break; case 2: printf("%f\n", msg.u.f); break; case 3: printf("%s\n", msg.u.str); break; } } int main() { struct message msg[3]; // 第一条信息为整型,type 为 1 msg[0].type = 1; msg[0].u.n = 123; // 第二条信息为浮点型,type 为 2 msg[1].type = 2; msg[1].u.f = 3.14159; // 第三条信息为字符串,type 为 3 msg[2].type = 3; msg[2].u.str = "HelloWorld"; for (int i = 0; i < 3; i++) { printMsg(msg[i]); } return 0; }与前面的示例相比,这段代码将结构体中的 int n、float f 和 char *str 成员替换为一个名为 u 的联合体。联合体 u 包含三个成员:int n、float f和char *str。
这种改进能够节省内存空间,因为使用联合体只会为 int n、float f和char *str 中最大的成员分配空间。联合体成员共享内存,因此整个结构体占用的空间会减少。
另外,还有一种匿名嵌套的写法。嵌套的 union 中没必要写明成员名 u。在其后的使用中,union 中的成员被当作 message 的成员一样处理。
struct message { int type; union { int n; float f; char *str; // 这里省略成员名 u 作为匿名嵌套成员 }; }; void printMsg(struct message msg) { switch (msg.type) { case 1: printf("%d\n", msg.n); // msg.u.n 被省略为 msg.n break; case 2: printf("%f\n", msg.f); // msg.u.f 被省略为 msg.f break; case 3: printf("%s\n", msg.str); // msg.u.str 被省略为 msg.str break; } }