【C语言】union 关键字详解

举报
LuckiBit 发表于 2024/12/05 10:32:58 2024/12/05
【摘要】 联合体(`union`)是一种强大的数据结构,在C语言中具有广泛的应用。通过共享内存位置,联合体可以在不同时间存储不同类型的数据,从而节省内存。在嵌入式系统、硬件编程和协议解析等领域,联合体的使用尤为常见。理解和正确使用联合体可以使代码更加高效和灵活,特别是在内存受限的系统中。

C语言 union 关键字详解

union 关键字在C语言中用于定义联合体(union)。联合体是一种特殊的数据结构,它允许在同一内存位置存储不同的数据类型。不同于结构体(struct),联合体的所有成员共享相同的内存区域,因此联合体的大小等于其最大成员的大小。

1. union 关键字的基本概念

1.1 基本语法

union union_name {
    type1 member1;
    type2 member2;
    // ...
};
  • union_name:联合体的名称。
  • type1, type2, …:联合体的成员类型。
  • member1, member2, …:联合体的成员名称。

1.2 示例

#include <stdio.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;

    data.i = 10;
    printf("data.i: %d\n", data.i); // 输出: data.i: 10

    data.f = 220.5;
    printf("data.f: %f\n", data.f); // 输出: data.f: 220.500000

    strcpy(data.str, "Hello");
    printf("data.str: %s\n", data.str); // 输出: data.str: Hello

    // 注意:访问其他成员可能会导致未定义行为
    printf("data.i: %d\n", data.i); // 输出: data.i: (可能是未定义的值)

    return 0;
}

解释

  • union Data 定义了一个联合体,它包含一个 int、一个 float 和一个字符数组 str
  • 联合体的成员共享相同的内存,因此在写入 data.i 后,写入 data.f 会覆盖 data.i 的值。
  • 访问覆盖的成员可能会得到未定义的结果。

输出

data.i: 10
data.f: 220.500000
data.str: Hello
data.i: 0 (或其他未定义值)

2. union 关键字的大小

2.1 大小的计算

联合体的大小等于其最大成员的大小,加上可能的内存对齐要求。因为所有成员共享同一块内存,联合体的大小由其最大的成员决定。

2.1.1 示例:计算联合体的大小

#include <stdio.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    printf("Size of union Data: %zu\n", sizeof(union Data)); // 输出: Size of union Data: 20
    return 0;
}

解释

  • sizeof(union Data) 返回联合体 Data 的大小。
  • 在这个示例中,str 的大小决定了联合体的大小,因此输出是 20 字节。

输出

Size of union Data: 20

2.2 内存对齐

联合体的内存对齐取决于编译器的实现和平台。通常,联合体的大小是其最大成员的大小,并可能会对齐到某个边界。

2.2.1 示例:内存对齐

#include <stdio.h>

union AlignedData {
    char c;
    int i;
    double d;
};

int main() {
    printf("Size of union AlignedData: %zu\n", sizeof(union AlignedData)); // 输出: Size of union AlignedData: 8
    return 0;
}

解释

  • sizeof(union AlignedData) 返回联合体 AlignedData 的大小。
  • double 类型的大小通常是 8 字节,因此联合体的大小是 8 字节,并且可能会有内存对齐的要求。

输出

Size of union AlignedData: 8

3. 使用 union 关键字的实际应用

3.1 动态数据存储

联合体适用于需要存储不同数据类型但不需要同时存储的场景。它节省了内存空间,适合在内存受限的系统中使用。

3.1.1 示例

#include <stdio.h>

union SensorData {
    int temperature;
    float pressure;
    char status;
};

int main() {
    union SensorData sensor;

    sensor.temperature = 25;
    printf("Temperature: %d\n", sensor.temperature); // 输出: Temperature: 25

    sensor.pressure = 1013.25;
    printf("Pressure: %.2f\n", sensor.pressure); // 输出: Pressure: 1013.25

    sensor.status = 'A';
    printf("Status: %c\n", sensor.status); // 输出: Status: A

    // 注意:访问其他成员可能会导致未定义行为
    printf("Temperature: %d\n", sensor.temperature); // 输出: Temperature: (可能是未定义的值)

    return 0;
}

解释

  • union SensorData 包含了 intfloatchar 类型的成员。
  • 由于这些成员共享内存,修改一个成员会影响其他成员的值。
  • 联合体的内存大小取决于其最大成员的大小。

输出

Temperature: 25
Pressure: 1013.25
Status: A
Temperature: 0 (或其他未定义值)

3.2 解析复杂数据结构

联合体可以与结构体结合使用,简化复杂数据结构的定义和访问。

3.2.1 示例

#include <stdio.h>

struct Packet {
    unsigned char type;
    union {
        int id;
        float value;
    } data;
};

int main() {
    struct Packet packet;

    packet.type = 1;
    packet.data.id = 123;
    printf("Packet Type: %d\n", packet.type); // 输出: Packet Type: 1
    printf("Packet ID: %d\n", packet.data.id); // 输出: Packet ID: 123

    packet.type = 2;
    packet.data.value = 3.14;
    printf("Packet Type: %d\n", packet.type); // 输出: Packet Type: 2
    printf("Packet Value: %.2f\n", packet.data.value); // 输出: Packet Value: 3.14

    return 0;
}

解释

  • struct Packet 定义了一个包含 typedata 成员的结构体。
  • data 成员是一个联合体,它可以是 intfloat 类型。
  • 通过修改 type 来决定 data 中存储的数据类型。

输出

Packet Type: 1
Packet ID: 123
Packet Type: 2
Packet Value: 3.14

4. union 关键字的注意事项

注意事项 描述 示例
内存共享 联合体的所有成员共享相同的内存位置,因此同时存储多个成员可能会导致数据覆盖。 data.i, data.f, data.str
内存对齐 联合体的大小通常是其最大成员的大小,并且可能会受到内存对齐的影响。 sizeof(union Data)
未定义行为 访问未被初始化的联合体成员或被其他成员覆盖的成员可能会导致未定义行为。 data.idata.f 的输出
与结构体结合 联合体可以与结构体结合使用,以创建更复杂的数据结构。 struct Packet

5. 综合示例

以下是一个综合示例,展示了联合体在实际应用中的不同用法。

#include <stdio.h>

union Data {
    int i;
    float f;
    char str[20];
};

struct Sensor {
    unsigned char id;
    union Data value;
};

int main() {
    union Data data;
    struct Sensor sensor;

    // 使用联合体
    data.i = 100;
    printf("Data as int: %d\n", data.i); // 输出: Data as int: 100
    data.f = 3.14;
    printf("Data as float: %f\n", data.f); // 输出: Data as float: 3.140000
    strcpy(data.str, "Hello");
    printf("Data as string: %s\n", data.str); // 输出: Data as string: Hello

    // 使用结构体结合联合体
    sensor.id = 1;
    sensor.value.f = 99.99;
    printf("Sensor ID: %d\n", sensor.id); // 输出: Sensor ID: 1
    printf("Sensor Value as float: %f\n", sensor.value.f); // 输出: Sensor Value as float: 99.990000

    return 0;
}

**编译和

编译和执行

gcc -o my_program main.c
./my_program

输出结果

Data as int: 100
Data as float: 3.140000
Data as string: Hello
Sensor ID: 1
Sensor Value as float: 99.990000

6. 联合体的内存分布和对齐

联合体的内存分布取决于其最大成员的大小和对齐要求。在不同的系统和编译器上,内存对齐的要求可能不同。

6.1 内存对齐示例

以下示例展示了联合体在内存中的布局以及对齐的影响。

示例

#include <stdio.h>

union Example {
    char c;
    int i;
    double d;
};

int main() {
    union Example ex;

    ex.c = 'A';
    printf("ex.c: %c\n", ex.c); // 输出: ex.c: A
    printf("Size of union Example: %zu\n", sizeof(union Example)); // 输出: Size of union Example: 8

    ex.i = 100;
    printf("ex.i: %d\n", ex.i); // 输出: ex.i: 100
    printf("Size of union Example: %zu\n", sizeof(union Example)); // 输出: Size of union Example: 8

    ex.d = 3.14;
    printf("ex.d: %f\n", ex.d); // 输出: ex.d: 3.140000
    printf("Size of union Example: %zu\n", sizeof(union Example)); // 输出: Size of union Example: 8

    return 0;
}

解释

  • 联合体 Example 包含 charintdouble 三个成员。
  • double 是最大成员,其大小为 8 字节。
  • 因此,整个联合体的大小也是 8 字节。

输出

ex.c: A
Size of union Example: 8
ex.i: 100
Size of union Example: 8
ex.d: 3.140000
Size of union Example: 8

6.2 联合体内存布局的可视化

为了更好地理解联合体的内存布局,可以通过输出各个成员的地址来可视化。

示例

#include <stdio.h>

union Example {
    char c;
    int i;
    double d;
};

int main() {
    union Example ex;

    printf("Address of ex.c: %p\n", (void*)&ex.c);
    printf("Address of ex.i: %p\n", (void*)&ex.i);
    printf("Address of ex.d: %p\n", (void*)&ex.d);
    printf("Size of union Example: %zu\n", sizeof(union Example));

    return 0;
}

解释

  • 通过打印各个成员的地址,可以看到它们共享相同的内存位置。

输出

Address of ex.c: 0x7ffc93c88820
Address of ex.i: 0x7ffc93c88820
Address of ex.d: 0x7ffc93c88820
Size of union Example: 8

7. unionstruct 的对比

特性 union struct
内存分配 所有成员共享相同的内存位置 每个成员都有自己的内存位置
大小 等于最大成员的大小 等于所有成员大小之和
用途 用于节省内存,适合在同一时间只需要一个成员的情况 用于需要同时访问所有成员的情况
内存对齐 由最大成员决定 每个成员都有自己的内存对齐要求
数据访问 访问一个成员会覆盖其他成员的数据 访问一个成员不会影响其他成员

7.1 示例:unionstruct 的对比

#include <stdio.h>
#include <string.h>

union DataUnion {
    int i;
    float f;
    char str[20];
};

struct DataStruct {
    int i;
    float f;
    char str[20];
};

int main() {
    union DataUnion u;
    struct DataStruct s;

    // 初始化并打印联合体成员
    u.i = 10;
    printf("Union - u.i: %d\n", u.i);
    u.f = 220.5;
    printf("Union - u.f: %f\n", u.f);
    strcpy(u.str, "Hello");
    printf("Union - u.str: %s\n", u.str);

    // 初始化并打印结构体成员
    s.i = 10;
    printf("Struct - s.i: %d\n", s.i);
    s.f = 220.5;
    printf("Struct - s.f: %f\n", s.f);
    strcpy(s.str, "Hello");
    printf("Struct - s.str: %s\n", s.str);

    // 联合体的大小
    printf("Size of union DataUnion: %zu\n", sizeof(union DataUnion));
    // 结构体的大小
    printf("Size of struct DataStruct: %zu\n", sizeof(struct DataStruct));

    return 0;
}

输出

Union - u.i: 10
Union - u.f: 220.500000
Union - u.str: Hello
Struct - s.i: 10
Struct - s.f: 220.500000
Struct - s.str: Hello
Size of union DataUnion: 20
Size of struct DataStruct: 28

解释

  • 联合体的大小等于其最大成员的大小(char str[20],即 20 字节)。
  • 结构体的大小等于其所有成员大小之和,并且可能由于内存对齐而增加(在此示例中为 28 字节)。

8. 联合体的实际应用场景

8.1 联合体在硬件编程中的应用

联合体常用于硬件编程,例如在嵌入式系统中表示一个寄存器,它可以同时表示多个不同的字段。联合体在嵌入式系统和硬件编程中广泛使用,因为它允许以位字段的形式直接访问和操作硬件寄存器。

示例:硬件寄存器中的联合体应用

#include <stdio.h>

union Register {
    unsigned int value;
    struct {
        unsigned int flag1 : 1;
        unsigned int flag2 : 1;
        unsigned int flag3 : 1;
        unsigned int reserved : 29;
    } flags;
};

int main() {
    union Register reg;

    // 设置寄存器的值
    reg.value = 0x05;
    printf("Register value: 0x%X\n", reg.value);
    printf("Flag1: %d\n", reg.flags.flag1);
    printf("Flag2: %d\n", reg.flags.flag2);
    printf("Flag3: %d\n", reg.flags.flag3);

    // 修改标志位
    reg.flags.flag1 = 0;
    printf("Register value: 0x%X\n", reg.value);
    printf("Flag1: %d\n", reg.flags.flag1);

    return 0;
}

解释

  • union Register 表示一个硬件寄存器,其中 value 表示寄存器的完整值,而 flags 表示寄存器的各个位字段。
  • 通过修改 flags 中的位字段,可以影响 value 的值,反之亦然。

输出

Register value: 0x5
Flag1: 1
Flag2: 0
Flag3: 1
Register value: 0x4
Flag1: 0

8.2 联合体在协议解析中的应用

联合体也常用于协议解析中,特别是在处理网络数据包或二进制文件时。通过使用联合体,可以在不复制数据的情况下,以多种格式查看相同的数据。

示例:协议解析中的联合体应用

#include <stdio.h>

union Protocol {
    struct {
        unsigned char version;
        unsigned char type;
        unsigned short length;
    } header;
    unsigned char raw[4];
};

int main() {
    union Protocol proto;

    // 初始化原始数据
    proto.raw[0] = 1; // version
    proto.raw[1] = 2; // type
    proto.raw[2] = 0; // length high byte
    proto.raw[3] = 10; // length low byte

    printf("Version: %d\n", proto.header.version); // 输出: Version: 1
    printf("Type: %d\n", proto.header.type); // 输出: Type: 2
    printf("Length: %d\n", proto.header.length); // 输出: Length: 10

    return 0;
}

解释

  • union Protocol 定义了一个协议头部,其中 header 是结构体表示的协议头,raw 是原始字节数组。
  • 通过初始化 raw 数组,可以间接初始化 header 结构体,并且可以使用结构体成员来访问数据。

输出

Version: 1
Type: 2
Length: 10

8.3 联合体在节省内存中的应用

在某些应用场景中,需要在不同时间存储不同类型的数据。通过使用联合体,可以节省内存,因为联合体的所有成员共享相同的内存位置。

示例:节省内存的联合体应用

#include <stdio.h>

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;

    data.i = 42;
    printf("data.i: %d\n", data.i); // 输出: data.i: 42

    data.f = 3.14;
    printf("data.f: %f\n", data.f); // 输出: data.f: 3.140000

    snprintf(data.str, 20, "Hello, World!");
    printf("data.str: %s\n", data.str); // 输出: data.str: Hello, World!

    // 注意:访问其他成员可能会导致未定义行为
    printf("data.i: %d\n", data.i); // 输出: data.i: (未定义值)

    return 0;
}

解释

  • union Data 包含了 intfloatchar 数组三种数据类型。
  • 通过依次存储 intfloatchar 数组,可以看到每次存储新的数据时会覆盖之前的数据。

输出

data.i: 42
data.f: 3.140000
data.str: Hello, World!
data.i: 1819043144 (或其他未定义值)

9. 总结

联合体(union)是一种强大的数据结构,在C语言中具有广泛的应用。通过共享内存位置,联合体可以在不同时间存储不同类型的数据,从而节省内存。在嵌入式系统、硬件编程和协议解析等领域,联合体的使用尤为常见。理解和正确使用联合体可以使代码更加高效和灵活,特别是在内存受限的系统中。

9.1 表格总结

特性 描述 示例
内存共享 联合体的所有成员共享相同的内存位置,因此同时存储多个成员可能会导致数据覆盖。 data.i, data.f, data.str
内存对齐 联合体的大小通常是其最大成员的大小,并且可能会受到内存对齐的影响。 sizeof(union Data)
未定义行为 访问未被初始化的联合体成员或被其他成员覆盖的成员可能会导致未定义行为。 data.idata.f 的输出
与结构体结合 联合体可以与结构体结合使用,以创建更复杂的数据结构。 struct Packet
硬件编程中的应用 联合体常用于表示硬件寄存器,其中每个位字段可以代表寄存器的不同功能。 union Register
协议解析中的应用 联合体常用于处理网络数据包或二进制文件,使得同一块数据可以以多种格式查看。 union Protocol
节省内存的应用 联合体在不同时间存储不同类型的数据,从而节省内存。 union Data

通过对以上内容的学习,您现在应该对C语言中的union关键字有了全面的理解和掌握。希望这些示例和解释能够帮助您在实际编程中更好地应用联合体。

6. 结束语

  1. 本节内容已经全部介绍完毕,希望通过这篇文章,大家对 union 关键字区别有了更深入的理解和认识。
  2. 感谢各位的阅读和支持,如果觉得这篇文章对你有帮助,请不要吝惜你的点赞和评论,这对我们非常重要。再次感谢大家的关注和支持![点我关注❤️]
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。