10

这个问题背后的想法是理解使用联合的更深层次的概念并以不同的方式使用它以节省内存。我对所有人的问题是 -

假设有一个结构

struct strt
{
   float f;
   char c;
   int a;
}

和 union 中表示的相同结构

union unin
{
   float f;
   char c;
   int a;
}

如果我一个接一个地为结构成员分配值然后打印它们,它就会被打印出来。但是在联合的情况下它不会发生,一些覆盖正在完成..

所以我需要找到一种可以使用 union 存储 f、c、a 值的方法,然后我可以打印出来。(应用任何操作或任何东西..)但我正在寻找这种技术..那里的任何人都可以指导我或给我任何想法吗?

4

6 回答 6

55

如果您要查看结构如何存储其值,它会是这样的:

|0---1---2---3---|4---|5---6---7---8---|
|ffffffffffffffff|    |                | <- f: Where your float is stored
|                |cccc|                | <- c: Where your char is stored
|                |    |aaaaaaaaaaaaaaaa| <- a: Where your int is stored

因此,当您更改 f 的值时,实际上是在更改字节 0-3。当您更改 char 时,实际上是在更改字节 4。当您更改 int 时,实际上是在更改字节 5-8。

如果你现在看看联合是如何存储它的值的​​,它会是这样的:

|0---1---2---3---|
|ffffffffffffffff| <- f: where your float is stored
|cccc------------| <- c: where your char is stored
|aaaaaaaaaaaaaaaa| <- a: where your int is stored

所以现在,当我改变 f 的值时,我正在改变字节 0-3。由于 c 存储在字节 0 中,所以当您更改 f 时,您也更改了 c 和 a!当你改变 c 时,你改变了 f 和 a 的一部分——当你改变 a 时,你改变了 c 和 f。这就是你的“覆盖”发生的地方。当您将 3 个值打包到一个内存地址中时,您根本没有“节省空间”;您只是创建了 3 种不同的方式来查看和更改相同的数据。在那个联合中你并没有真正的 int、float 和 char——在物理层面上,你只有 32 位,可以被视为 int、float 或 char。改变一个意味着改变另一个。如果您不希望它们相互更改,请使用结构。

这就是为什么 gcc 告诉你你的 struct 是 9 个字节长,而你的 union 只有 4 个字节——它没有节省空间——只是 struct 和 union 不是一回事。

于 2009-04-07T06:05:41.037 回答
39

我认为您误解了 a 的目的union

union顾名思义,A定义了一个结构,其中所有成员都占用相同的内存空间。而 astruct将其每个成员放在单独的内存中,位于单个连续区域中。

与你的工会一起,当你写:

union foo;
foo.c = 3;

然后foo.afoo.f都将被改变。这是因为.a,.c.f存储在同一个内存位置。因此,联合的每个成员都是同一内存的不同“视图”。a 不会发生这种情况,struct因为所有成员都是不同的并且彼此分开。

没有办法解决这种行为,因为它是故意的。

于 2009-04-07T05:44:16.393 回答
12

我认为你误解了工会。

使用联合背后的想法是节省内存......

是的,这是一个原因

...并获得与结构等效的结果...

它不等价。它们在源代码中看起来很相似,但它是完全不同的东西。像苹果和飞机。

联合是一个非常非常低级的结构,它允许您查看一块内存,就像存储它的任何“成员”一样,但您一次只能使用一个。甚至使用“成员”这个词也极具误导性。它们应该被称为“视图”或其他东西,而不是成员。

当你写:

union ABCunion
{
    int a;
    double b;
    char c;
} myAbc;

你是说:“取一块足够大的内存来容纳 int、char 和 double 中最大的内存,我们称之为myAbc

在该内存中,现在您可以存储intdoublechar。如果你存储一个 int,然后存储一个 double,这个 int 就永远消失了。

那有什么意义呢?

联合有两个主要用途。

a) 区分存储

这就是我们上面所做的。我选择一段记忆,并根据上下文赋予它不同的含义。有时上下文是显式的(您保留一些变量来指示您存储的变量的“种类”),有时它可能是隐式的(根据代码部分,您可以判断必须使用哪个变量)。无论哪种方式,代码都需要能够弄清楚,否则您将无法对变量执行任何明智的操作。

一个典型的(显式)示例是:

struct MyVariantType
{
    int typeIndicator ;  // type=1 -> It's an int, 
                         // type=2 -> It's a  double, 
                         // type=3 -> It's a  char
    ABCunion body;
};

例如,VB6 的“变体”是与上述不同的联合(但更复杂)。

b) 拆分表示 当您需要能够将变量视为“整体”或部分组合时,这有时很有用。用一个例子更容易解释:

union DOUBLEBYTE
{
    struct
    {
        unsigned char a;
        unsigned char b;
    } bytes;
    short Integer;        
} myVar;

这是一个带有一对字节的短整数“联合”。现在,您可以查看与短整数 (myVar.Integer) 相同的值,也可以轻松研究构成该值一部分的各个字节(myVar.bytes.a 和 myVar.bytes.b)。

请注意,第二次使用是不可移植的(我很确定);这意味着它不能保证在不同的机器架构上工作;但是这种使用对于 C 设计的任务类型(操作系统实现)是绝对必要的。

于 2009-04-07T06:20:00.160 回答
9

联合包含一组互斥数据。

在您的特定示例中,您可以将 float ( f )、char ( c ) 或 int ( a ) 存储在联合中。但是,只会为联合中最大的项目分配内存。联合中的所有项目将共享相同的内存部分。换句话说,将一个值写入联合,然后再写入另一个值将导致第一个值被覆盖。

你需要回去问问自己你在建模什么

  • 您是否真的希望fca的值互斥(即一次只能存在一个值)?如果是这样,请考虑将联合与枚举值(存储在联合之外)结合使用,以指示联合中的哪个成员在任何特定时间点是“活动的”成员。这将允许您以更危险的代码为代价获得使用联合的内存优势(因为任何维护它的人都需要知道这些值是互斥的 - 即它确实是一个联合)。 仅当您要创建许多这些联合并且内存保护至关重要(例如在嵌入式 CPU 上)时才考虑此选项。您甚至可能最终不会节省内存,因为您需要在堆栈上创建枚举变量,这也会占用内存。

  • 您是否希望这些值同时处于活动状态且不会相互干扰?如果是这样,您将需要使用结构来代替(正如您在第一个示例中所说的那样)。这将使用更多内存 - 当您实例化一个结构时,分配的内存是所有成员的总和(加上一些填充到最近的单词边界)。 除非内存保护是最重要的(参见前面的示例),否则我会赞成这种方法。

编辑:

(非常简单)如何将枚举与联合一起使用的示例:

typedef union
{
    float f;
    char c;
    int a;
} floatCharIntUnion;

typedef enum
{
    usingFloat,
    usingChar,
    usingInt
} unionSelection;

int main()
{
    floatCharIntUnion myUnion;
    unionSelection selection;

    myUnion.f = 3.1415;
    selection = usingFloat;
    processUnion(&myUnion, selection);

    myUnion.c = 'a';
    selection = usingChar;
    processUnion(&myUnion, selection);

    myUnion.a = 22;
    selection = usingInt;
    processUnion(&myUnion, selection);
}

void processUnion(floatCharIntUnion* myUnion, unionSelection selection)
{

    switch (selection)
    {
    case usingFloat:
        // Process myUnion->f
        break;
    case usingChar:
        // Process myUnion->c
        break;
    case usingInt:
        // Process myUnion->a
        break;
    }
}
于 2009-04-07T05:44:54.783 回答
1

这是一个使用联合来存储数据的经典示例,具体取决于外部标记。

int、float 和 char * 都在联合中占据相同的位置,它们不是连续的,所以如果你需要将它们全部存储,它是你正在寻找的结构,而不是联合。

结构是联合中最大事物的大小加上类型的大小,因为它在联合之外。

#define TYP_INT 0
#define TYP_FLT 1
#define TYP_STR 2

typedef struct {
    int type;
    union data {
        int a;
        float b;
        char *c;
    }
} tMyType;

static void printMyType (tMyType * x) {
    if (x.type == TYP_INT) {
        printf ("%d\n", x.data.a;
        return;
    }
    if (x.type == TYP_FLT) {
        printf ("%f\n", x.data.b;
        return;
    }
    if (x.type == TYP_STR) {
        printf ("%s\n", x.data.c;
        return;
    }
}

printMyType 函数将正确检测结构中存储的内容(除非您对其撒谎)并打印出相关值。

当您填充其中之一时,您必须执行以下操作:

x.type = TYP_INT;
x.data.a = 7;

或者

x.type = TYP_STR;
x.data.c = "Hello";

一个给定x的一次只能是一件事。

任何尝试的人都有祸了:

x.type = TYP_STR;
x.data.a = 7;

他们是在自找麻烦。

于 2009-04-07T06:09:29.390 回答
0

当在任何给定时间点仅将以下其中一项存储在实例中时,通常会使用联合。即,您可以随时存储浮点数、字符或整数。这是为了节省内存 - 当您打算使用它来存储字符时,不为浮点数和整数分配额外/不同的内存。分配的内存量 = union 中最大的类型。

union unin
{
   float f;
   char c;
   int a;
}

union 的另一个用途是当您想要存储具有部分的东西时,让 sat 您可能希望将寄存器建模为包含高字节、低字节和复合值的联合。因此,您可以将复合值存储到联合中,并使用成员通过其他成员获取片段。

于 2009-04-07T05:48:17.787 回答