C中的联合与结构

时间:2009-04-07 05:39:51

标签: c unions

这个问题背后的想法是理解使用联合的更深层次的概念,并以不同的方式使用它以节省记忆。我的问题是 -

让我们说有一个结构

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

和联合中表示的相同结构

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

如果我一个接一个地为结构成员分配值然后打印它们就会得到 打印。但是如果没有发生联盟就会发生一些覆盖......

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

6 个答案:

答案 0 :(得分:49)

如果你要看一个struct如何存储它的值,它将是这样的:

|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。更改字符时,实际上是在更改字节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告诉你你的结构长度为9个字节,而你的结合只有4个 - 它没有节省空间 - 只是结构和联合不是一回事。

答案 1 :(得分:38)

我认为你误解了union的目的。

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

与你的工会,当你写:

union foo;
foo.c = 3;

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

没有办法绕过这种行为因为它是故意的。

答案 2 :(得分:12)

我认为你误解了工会。

  

使用工会背后的想法就是节省内存......

是的,这是一个原因

  

...并获得相当于结构的结果......

没有

它不等同。它们在源代码中看起来很相似,但它完全不同。像苹果和飞机一样。

Unions是一个非常非常低级别的构造,它允许您看到一块内存,就像存储它的任何“成员”一样,但您一次只能使用一个。即使使用“成员”一词也极具误导性。它们应该被称为“观点”或其他东西,而不是成员。

当你写:

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

你说:“把一块足够大的内存放到一个int,一个char和一个double中的最大内存,然后把它叫做 myAbc

在该内存中,现在您可以 一个int,一个double,一个char。如果存储一个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的“Variants”是Unions,与上述不同(但更复杂)。

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

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

这是一个带有一对字节的短“int”“unioned”。现在,您可以查看与short int(myVar.Integer)相同的值,或者您可以轻松地研究构成值的各个字节(myVar.bytes.a和myVar.bytes.b)。 / p>

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

答案 3 :(得分:9)

联合包含一组互斥数据。

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

您需要回过头来问自己您的建模

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

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

修改

(非常简单)如何将enums与union结合使用的示例:

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;
    }
}

答案 4 :(得分:1)

这是使用联合根据外部标记存储数据的典型示例。

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

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

#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;

他们在寻找麻烦。

答案 5 :(得分:0)

当在任何给定的时间点仅将下面的一个存储在实例中时,通常使用联合。即你可以在任何时刻存储一个浮点数,一个char或一个int。这是为了节省内存 - 当你打算用它来存储一个char时,不为float和int分配额外/不同的内存。分配的内存量=联合中的最大类型。

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

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