C++ 联合体

一、创建与使用联合体

1.1 概念

联合体(union)看起来像结构体,但其成员共享同一段内存:编译器只为联合体分配一块足够大的内存——大小等于其最大成员的大小,并按成员的最大对齐要求对齐。因为所有成员从同一地址开始,所以在某一时刻只有一个成员能被“有效地”保存原始语义;如果写入了一个成员然后读取另一个成员,得到的是相同内存的不同解释

特别之处

"联合"与"结构"有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间,一个结构体变量的总长度大于等于各成员长度之和。而在"联合"中,各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。

应该说明的是,这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值。

1.2 基本特性

  • 所有成员共享同一块内存空间;
  • 联合体的大小等于其最大成员的大小;
  • 同一时间只能访问一个成员,修改一个成员会影响其他成员;

变量内存共用示例

#include <iostream>
using namespace std;

// 定义联合体
union Data
{
    int i; // 4字节
    int f; // 4字节
};

int main()
{
    union Data data;
    data.i = 10;
    // 10
    cout << "整数值: " << data.i << endl;

    data.f = 12;
    // 12
    cout << "浮点值: " << data.f << endl;
    // 此时整数已经访问结果已经变了
    // 12
    cout << "整数值: " << data.i << endl;
    return 0;
}

不同类型成员示例

#include <iostream>
using namespace std;

// 在一个时间内中使用一个变量:
union Data
{
    int i;
    double f;
};

int main()
{
    cout << sizeof(Data) << endl; // 8
    Data data;
    data.i = 12;
    cout << data.i << endl;    // 12
    data.f = 999;
    cout << data.i << endl;    // 值被覆盖
    cout << data.f << endl;    // 999
    return 0;
}

二、联合体的特点探讨

2.1 内存分析

联合体成员共用一个内存空间,那么一定要保证最大的成员要装下,所以这样的联合变量的大小,至少是最大成员的大小(因为联合体至少有能力保存最大的那个成员)。

#include <stdio.h>
#include <stdlib.h>

union Un
{
    char a;
    int i;
};

struct Stc
{
    char a;
    int i;
};

int main()
{
    union Un un = { 0 };
    // 4
    printf("%d\n", sizeof(un));
    
    // 8
    struct Stc stc = { 0 };
    printf("%d\n", sizeof(stc));
    
    printf("%d\n", sizeof(un.a)); // 1
    printf("%d\n", sizeof(un.i)); // 4
    printf("%p\n", &un);         // 0060FEFC
    printf("%p\n", &(un.a));     // 0060FEFC
    printf("%p\n", &(un.i));     // 0060FEFC
    
    system("pause");
    return 0;
}

说明:联合体及其各个成员们的地址都是一样的,说明所有成员都在首地址处存放。

2.2 内存覆盖测试

通过写入 int 再写 char 来演示低字节被覆盖的现象。要理解输出,必须知道系统的字节序(endianness):

  • 小端(little-endian):低位字节放在低地址。写 i = 0x11223344 后内存从低地址到高地址是 44 33 22 11,如果再写 a = 0x55 会覆盖第一个字节(低位),结果变为 55 33 22 11, 整数是 0x11223355(在打印时可能以小端理解)。
  • 大端(big-endian):高位字节放在低地址,内存为 11 22 33 44,写低地址的 char 会覆盖高位字节,结果不同。

假设小端系统(x86/x86_64 通常是小端):

#include <stdio.h>
#include <stdlib.h>

union Un
{
    int i;
    int j;
    char a;
    char b;
};

int main()
{
    // 测试1
    union Un un1 = { 0 };
    un1.i = 0x11223344;
    un1.a = 0x55;
    // 0x11223355
    printf("%x\n", un1.i);
    
    // 测试2
    union Un un2 = { 0 };
    un2.i = 0x1122;
    un2.a = 0x99;
    // 0x1199
    printf("%x\n", un2.i);
    
    // 测试3
    union Un un3 = { 0 };
    un3.a = 0x11;
    un3.b = 0x88;
    // 0x88
    printf("%x\n", un3.i);
    
    // 测试4
    union Un un4 = { 0 };
    un4.i = 0x11002200;
    un4.b = 0x88;
    // 0x11002288
    printf("%x\n", un4.i);
    
    // 测试5
    union Un un5 = { 0 };
    un5.i = 0x11002200;
    un5.j = 0x88;
    // 0x88
    printf("%x\n", un5.i);
    
    system("pause");
    return 0;
}

说明:内存只有一块,大小为最大数据类型的大小,其它变量赋值,会从低位向高位覆盖。

2.3 大小的计算

联合体大小计算规则:

  1. 联合体的大小至少是最大成员的大小;
  2. 当最大成员大小不是最大对齐数的整数倍的时候,就要将最大成员大小对齐到最大对齐数的整数倍;
#include <stdio.h>
#include <stdlib.h>

union S1
{
    char a[5];  // 5字节
    int i;      // 4字节
};

union S2
{
    short c[7]; // 14字节
    int i;      // 4字节
};

union S3
{
    char a[8];  // 8字节
    int i;      // 4字节
};

int main()
{
    printf("%d\n", sizeof(union S1)); // 8
    printf("%d\n", sizeof(union S2)); // 16
    printf("%d\n", sizeof(union S3)); // 8
    system("pause");
    return 0;
}

分析

  • 在S1中,char a[5]的内存大小为5,int i内存大小为4,5不是4的整数倍,要对齐到最大对齐数的整数倍,所以是5 + 3 = 8,是4的倍数
  • S2的推导结果类似S1:14不是4的整数倍,对齐到16
  • S3中char a[8]是8字节,已经是4的倍数,所以大小为8

2.4 优点

联合体用于节省空间(不同类型互斥使用)非常有效。常见场景包括:协议解析、硬件寄存器映射、节省内存的变体信息存储等。

例如我们要统计三种商品数据:图书、杯子、衬衫。

每一种商品都有:库存量、价格、商品类型和商品类型相关的其他信息。

  • 图书:书名、作者、页数
  • 杯子:设计
  • 衬衫:设计、可选颜色、可选尺寸

使用结构体的方式(浪费内存)

#include<stdio.h>
struct my_list
{
    int stock_number;    // 库存量
    double price;        // 价格
    int item_type;       // 商品类型
    char title[20];      // 书名
    char author[20];     // 作者
    int num_pages;       // 页数
    char design[30];     // 设计
    int colors;          // 颜色
    int sizes;           // 尺寸
};

上述结构设计简单用起来方便,但结构的设计中包含了所有商品的各种属性,这样会使得结构体的大小过大,很浪费内存。因为对于各自商品,只有部分属性信息是常用的:商品是图书,就不需要design、colors、sizes。

使用联合体的优化方式

struct my_list
{
    // 公共属性
    int stock_number;    // 库存量
    double price;        // 价格
    int item_type;       // 商品类型
    union
    {
        struct           // 匿名结构体 - 图书
        {
            char title[20];   // 书名
            char author[20];  // 作者
            int num_pages;    // 页数
        } book;
        struct           // 匿名结构体 - 杯子
        {
            char design[30];  // 设计
        } mug;
        struct           // 匿名结构体 - 衬衫
        {
            char design[30];  // 设计
            int colors;       // 颜色
            int sizes;        // 尺寸
        } shirt;
    } item;
};

这种方式可以显著节省内存,因为同一时间只会使用其中一种商品类型的属性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值