什么时候应该使用工会?我们为什么需要它们?


当前回答

我想说,它可以更容易地重用可能以不同方式使用的内存,即节省内存。例如,你想做一些“变体”结构体,能够保存一个短字符串以及一个数字:

struct variant {
    int type;
    double number;
    char *string;
};

在32位系统中,这将导致每个变体实例至少使用96位或12个字节。

使用联合可以将大小减小到64位或8字节:

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};

如果你想添加更多不同的变量类型,你甚至可以保存更多。这可能是真的,你可以做类似的事情,强制转换一个空指针-但联合使它更容易访问,以及类型安全。这样的节省听起来并不是很大,但是您节省了用于该结构的所有实例的三分之一的内存。

其他回答

联合在嵌入式编程或需要直接访问硬件/内存的情况下特别有用。这里有一个简单的例子:

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

然后,您可以按如下方式访问reg:

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

字节顺序和处理器体系结构当然很重要。

另一个有用的特性是位修饰符:

typedef union
{
    struct {
        unsigned char b1:1;
        unsigned char b2:1;
        unsigned char b3:1;
        unsigned char b4:1;
        unsigned char reserved:4;
    } bits;
    unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;

使用这段代码,您可以直接访问寄存器/内存地址中的单个位:

x = reg.bits.b2;

当你有一个函数,你返回的值可以不同,这取决于函数做了什么,使用联合。

联合允许互斥的数据成员共享相同的内存。当内存比较稀缺时,例如在嵌入式系统中,这是非常重要的。

示例如下:

union {
   int a;
   int b;
   int c;
} myUnion;

这个联合将占用一个int值的空间,而不是3个独立的int值。如果用户设置了a的值,然后设置了b的值,它将覆盖a的值,因为它们都共享相同的内存位置。

这里有一个来自我自己代码库的联合的例子(来自记忆和转述,所以可能不准确)。它被用来在我构建的解释器中存储语言元素。例如,以下代码:

set a to b times 7.

由以下语言元素组成:

[设置]符号 可变[a] 符号[到] 可变[b] 符号[时报] 康斯坦[7] 符号[。]

语言元素被定义为“#define”值,如下:

#define ELEM_SYM_SET        0
#define ELEM_SYM_TO         1
#define ELEM_SYM_TIMES      2
#define ELEM_SYM_FULLSTOP   3
#define ELEM_VARIABLE     100
#define ELEM_CONSTANT     101

下面的结构被用来存储每个元素:

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;

然后,每个元素的大小是最大联合的大小(typ为4字节,联合为4字节,尽管这些是典型值,但实际大小取决于实现)。

为了创建一个“set”元素,你可以使用:

tElem e;
e.typ = ELEM_SYM_SET;

为了创建一个“variable[b]”元素,你可以使用:

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

为了创建一个常量[7]元素,你可以使用:

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

你可以很容易地将其扩展为包含浮点数(float flt)或有理数(struct ratnl {int num;Int denom;})和其他类型。

基本前提是str和val在内存中不是连续的,它们实际上是重叠的,所以这是一种在同一块内存上获得不同视图的方法,如图所示,其中结构基于内存位置0x1010,整数和指针都是4字节:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+

如果只是在一个结构中,它看起来会是这样的:

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+

一个简单而有用的例子是....

想象一下:

你有一个uint32_t数组[2],想要访问字节链的第3个和第4个字节。 你可以做*((uint16_t*) &数组[1])。 但遗憾的是,这打破了严格的混叠规则!

但是已知的编译器允许你做以下事情:

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}

严格来说,这仍然是违反规则的。但是所有已知的标准都支持这种用法。