C中的联合与结构
这个问题背后的想法是理解使用联合的更深层次的概念,并以不同的方式使用它以节省记忆。我的问题是 – 我的问题是 –
让我们说有一个结构
struct strt { float f; char c; int a; }
并且在联合中表示相同的结构
union unin { float f; char c; int a; }
如果我一个接一个地为结构成员分配值然后打印它们,它就会打印出来。 但是在联盟的情况下它不会发生,一些覆盖正在进行中。
所以我需要找到一个方法,它可以存储f,c,a的值使用union,然后我可以打印相同的。 (应用任何操作或任何东西..)但我正在寻找这种技术..任何人可以指导我或给我任何想法?
如果你要看一个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。 更改char时,实际上是在更改字节4.更改int时,实际上是在更改字节5-8。
如果你现在看一下union如何存储它的值,它将是这样的:
|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个 - 它不能节省空间 - 它只是结构和联合不是同一个东西。
我想你误解了union
的目的。
顾名思义,联合定义了一个结构,其中所有成员占用相同的内存空间 。 而struct
将其每个成员放在单独的连续区域中的单独内存中。
与你的工会,当你写:
union foo; foo.c = 3;
然后foo.a
和foo.f
都将被更改。 这是因为.a
, .c
和.f
存储在同一个内存位置 。 因此,联合的每个成员都是同一记忆的不同“视图”。 struct
不会发生这种情况,因为所有成员都是不同的并且彼此分开。
没有办法绕过这种行为, 因为它是故意的。
我想你是对工会的误解。
使用工会背后的想法是节省内存……
是的,这是一个原因
…并获得相当于结构的结果……
没有
它不等同。 它们在源代码中看起来很相似,但它完全不同。 像苹果和飞机。
联合是一个非常非常低级别的构造,它允许您看到一块内存,就像存储它的任何“成员”一样,但您一次只能使用一个 。 即使使用“成员”一词也极具误导性。 它们应该被称为“观点”或其他东西,而不是成员。
当你写:
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的“变体”是Unions,与上述不同(但更复杂)。
b)拆分表示当您需要能够将变量视为“整体”或部件组合时,这有时很有用。 用一个例子来解释更容易:
union DOUBLEBYTE { struct { unsigned char a; unsigned char b; } bytes; short Integer; } myVar;
这是一个短的int“联合”,带有一对字节。 现在,您可以查看与short int(myVar.Integer)相同的值,或者您可以轻松地研究构成值的各个字节(myVar.bytes.a和myVar.bytes.b)。
请注意,第二次使用不可移植(我很确定); 意味着它不能保证在不同的机器架构中工作; 但这种使用对于C设计的任务类型(OS实现)是绝对必要的。
联合包含一组互斥的数据。
在您的特定示例中,您可以将float( f ),char( c )或int( a )存储在union中。 但是,只会为联合中的最大项目分配内存。 联合中的所有项目将共享相同的内存部分 。 换句话说,将一个值写入联合后跟另一个值将导致第一个值被覆盖。
你需要回去问问自己你在建模什么 :
-
你真的希望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; } }
这是使用联合根据外部标记存储数据的典型示例。
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;
他们要求麻烦。
当在任何给定的时间点仅将下面的一个存储在实例中时,通常使用联合。 即你可以在任何时刻存储一个浮点数,一个char或一个int。 这是为了节省内存 – 当你打算用它来存储一个char时,不为float和int分配额外的/不同的内存。 分配的内存量=联合中的最大类型。
union unin { float f; char c; int a; }
union的另一个用途是当你想要存储具有部分的东西时,让你可能想要将寄存器建模为包含高字节,低字节和复合值的联合。 因此,您可以将合成值存储到联合中,并使用成员通过其他成员获取碎片。