结构和联合之间的区别


411

有没有很好的例子来说明a struct和a 之间的区别union?基本上,我知道会struct使用其成员的所有内存,并union使用最大的成员内存空间。操作系统级别是否有其他差异?

Answers:


677

使用联合,您只应使用其中一个元素,因为它们都存储在同一位置。当您要存储可能是几种类型之一的东西时,这很有用。另一方面,一个结构对其每个元素都有一个单独的存储位置,并且所有这些元素都可以一次使用。

为了具体说明它们的用法,我前一段时间正在研究Scheme解释器,实际上是将Scheme数据类型覆盖在C数据类型上。这涉及在结构中存储一个指示值类型的枚举和一个用于存储该值的联合。

union foo {
  int a;   // can't use both a and b at once
  char b;
} foo;

struct bar {
  int a;   // can use both a and b simultaneously
  char b;
} bar;

union foo x;
x.a = 3; // OK
x.b = 'c'; // NO! this affects the value of x.a!

struct bar y;
y.a = 3; // OK
y.b = 'c'; // OK

编辑:如果您想知道将xb设置为'c'会将xa的值更改为哪种,从技术上讲它是未定义的。在大多数现代机器上,char是1字节,int是4字节,因此给xb值'c'也给xa的第一个字节相同的值:

union foo x;
x.a = 3;
x.b = 'c';
printf("%i, %i\n", x.a, x.b);

版画

99, 99

为什么两个值相同?因为int 3的最后3个字节全为零,所以也将其读取为99。如果我们为xa输入更大的数字,您会发现情况并非总是如此:

union foo x;
x.a = 387439;
x.b = 'c';
printf("%i, %i\n", x.a, x.b);

版画

387427, 99

为了更仔细地查看实际的内存值,让我们以十六进制设置和打印这些值:

union foo x;
x.a = 0xDEADBEEF;
x.b = 0x22;
printf("%x, %x\n", x.a, x.b);

版画

deadbe22, 22

您可以清楚地看到0x22在哪里覆盖了0xEF。

在C语言中,未定义 int字节的顺序在我的Mac上,该程序用0x22覆盖了0xEF,但是在其他平台上,它将覆盖0xDE,因为组成int的字节顺序被颠倒了。因此,在编写程序时,您永远不要依赖于覆盖联合中的特定数据的行为,因为它不可移植。

有关字节顺序的更多信息,请查看endianness


1
使用此示例,在并集中,如果xb ='c',则xa中存储了什么?它是char的引用号吗?
kylex

1
希望能更详细地说明设置xb时xa中存储的内容
Kyle Cronin'Dec

1
@KyleCronin我想我明白了。在您的情况下,您有一组类型,知道只需要使用一种,但是直到运行时才知道哪一种-因此,联合允许您这样做。谢谢
user12345613 '02

2
@ user12345613联合可用作某种结构的基类。您可以使用结构的联合来模拟OO层次结构
Morten Jensen

1
@Lazar字节在多字节类型中的顺序取决于字节顺序。我建议阅读有关它的Wikipedia文章。
凯尔·克罗宁

83

简短的答案是:结构是一个记录结构:结构中的每个元素都分配新的空间。因此,像

struct foobarbazquux_t {
    int foo;
    long bar;
    double baz; 
    long double quux;
}

(sizeof(int)+sizeof(long)+sizeof(double)+sizeof(long double))为每个实例在内存中至少分配字节。(“至少”是因为架构对齐约束可能会迫使编译器填充该结构。)

另一方面,

union foobarbazquux_u {
    int foo;
    long bar;
    double baz; 
    long double quux;
}

分配一个内存块,并为其分配四个别名。因此sizeof(union foobarbazquux_u) ≥ max((sizeof(int),sizeof(long),sizeof(double),sizeof(long double)),再次可能有一些额外的对齐方式。


53

有没有什么好例子可以说明“结构”和“联合”之间的区别?

虚构的通讯协议

struct packetheader {
   int sourceaddress;
   int destaddress;
   int messagetype;
   union request {
       char fourcc[4];
       int requestnumber;
   };
};

在这个虚构的协议中,已经根据“消息类型”进行了区分,标头中的以下位置将是一个请求号,或者是一个四字符代码,但不能同时是两者。简而言之,并集允许相同的存储位置表示一种以上的数据类型,从而保证了您一次只想存储一种数据类型。

联合很大程度上是基于C的传统,作为系统编程语言的底层细节,有时以这种方式使用“重叠”存储位置。有时,您可以在具有数据结构的情况下使用联合来保存内存,在该数据结构中一次只能保存几种类型中的一种。

通常,操作系统不关心或不了解结构和联合-它们都是它们的简单存储块。结构是一块存储多个数据对象的内存块,这些对象不重叠。联合是存储多个数据对象的内存块,但是仅存储其中最大的一个,因此在任何时候只能存储一个数据对象。


1
是的 这很好地解释了用例!
基甸2013年

1
假设您有一个packetheader ph;访问请求号的方法?ph.request.requestnumber
justin.m.chase 2015年

最好的解释!谢谢。
84RR1573R

39

正如您已经在问题中指出的那样,union和之间的主要区别structunion成员之间相互覆盖,因此,union的sizeof是一个,而struct成员之间则是一个接一个地布置(之间有可选的填充)。同样,联合的大小足以容纳其所有成员,并且具有适合其所有成员的对齐方式。因此,假设int只能存储在2个字节的地址中,并且是2字节宽,而long只能存储在4个字节的地址中,并且是4字节长。以下联盟

union test {
    int a;
    long b;
}; 

可以有一个sizeof4,对齐要求是4。联合和结构都可以在结尾处填充,但不能在开头填充。写入结构只会更改写入成员的值。写入工会成员将使所有其他成员的值无效。如果您之前没有写过它们,则无法访问它们,否则行为是不确定的。GCC提供了一个扩展,您实际上可以从工会成员那里阅读,即使您最近没有写信给他们。对于操作系统,用户程序写入联合还是结构都没有关系。这实际上只是编译器的问题。

union和struct的另一个重要属性是,它们允许指向它们的指针可以指向其任何成员的类型。因此,以下内容是有效的:

struct test {
    int a;
    double b;
} * some_test_pointer;

some_test_pointer可以指向int*double*。如果将类型的地址强制转换testint*,它将a实际上指向其第一个成员。工会也是如此。因此,由于联合将始终具有正确的对齐方式,因此您可以使用联合使指向某种类型的指针有效:

union a {
    int a;
    double b;
};

该联合实际上将能够指向一个int和一个double:

union a * v = (union a*)some_int_pointer;
*some_int_pointer = 5;
v->a = 10;
return *some_int_pointer;    

如C99标准所述,实际上是有效的:

一个对象只能通过具有以下类型之一的左值表达式访问其存储值:

  • 与对象的有效类型兼容的类型
  • ...
  • 成员中包括上述类型之一的聚合或联合类型

编译器不会优化,v->a = 10;因为它可能会影响的值*some_int_pointer(并且函数将返回10而不是5)。


18

A union在几种情况下很有用。 union可以是用于非常低级操作的工具,例如为内核编写设备驱动程序。

一个例子是float通过使用带位域union的a struct和a 来解剖数字float。我保存在一个数float,以后我可以访问的特定部分float通过struct。该示例说明了如何union使用不同的角度查看数据。

#include <stdio.h>                                                                                                                                       

union foo {
    struct float_guts {
        unsigned int fraction : 23;
        unsigned int exponent : 8;
        unsigned int sign     : 1;
    } fg;
    float f;
};

void print_float(float f) {
    union foo ff;
    ff.f = f;
    printf("%f: %d 0x%X 0x%X\n", f, ff.fg.sign, ff.fg.exponent, ff.fg.fraction);

}

int main(){
    print_float(0.15625);
    return 0;
}

看一下维基百科上的单精度描述。我使用了示例,然后使用了魔幻数字0.15625。


union也可以用于实现具有多种选择的代数数据类型。我在O'Sullivan,Stewart和Goerzen撰写的“真实世界Haskell”一书中找到了一个例子。在“有区别的联合”部分中进行检查。

干杯!


11

union ”和“ struct ”是C语言的构造。谈论它们之间的“ OS级别”差异是不合适的,因为如果您使用一个或另一个关键字,则是由编译器生成不同的代码。


11

从技术上讲不是:

假设:椅子=记忆块,人=变量

结构:如果有3个人,他们可以相应地坐在自己大小的椅子上。

联合会:如果有3个人,只能坐在一张椅子上,当他们想坐的时候都需要使用同一把椅子。

从技术上讲是指:

下面提到的程序深入研究了结构和结合在一起。

struct MAIN_STRUCT
{
UINT64 bufferaddr;   
union {
    UINT32 data;
    struct INNER_STRUCT{
        UINT16 length;  
        UINT8 cso;  
        UINT8 cmd;  
           } flags;
     } data1;
};

MAIN_STRUCT总大小= bufferaddr的sizeof(UINT64)+联合的sizeof(UNIT32)+填充的32位(取决于处理器体系结构)= 128位。对于结构,所有成员都连续获取内存块。

Union获得最大大小成员的一个存储块(此处为32位)。在并集内部还有一个结构(INNER_STRUCT),其成员获得一个总大小为32位(16 + 8 + 8)的存储块。结合使用INNER_STRUCT(32位)成员 data(32位)都可以访问。


很好的解释。干杯!
炳廷

11

是的,struct和union之间的主要区别与您所述的相同。Struct使用其成员的所有内存,而union使用最大的成员内存空间。

但是所有区别都在于内存的使用需求。联合的最佳用法可以在我们使用信号的unix过程中看到。就像一个过程一次只能作用于一个信号一样。因此,一般声明为:

union SIGSELECT
{
  SIGNAL_1 signal1;
  SIGNAL_2 signal2;
  .....
};

在这种情况下,过程仅使用所有信号中最高的内存。但是如果在这种情况下使用struct,则内存使用量将为所有信号的总和。有很大的不同。

总而言之,如果您知道一次访问任何一个成员,则应选择“联合”。


10

您拥有了,仅此而已。但是,基本上,工会的意义何在?

您可以在同一位置放置不同类型的内容。您必须知道存储在联合体中的类型(因此通常将其放入struct带有类型标记的...中)。

为什么这很重要?并非真正为了节省空间。是的,您可以获取一些位或进行一些填充,但这不再是重点。

这是出于类型安全的考虑,它使您能够执行某种“动态类型化”:编译器知道您的内容可能具有不同的含义,以及运行时如何解释内容的确切含义。如果您有一个可以指向不同类型的指针,则必须使用联合,否则由于别名问题,您的代码可能不正确(编译器自言自语“哦,只有该指针可以指向该类型,因此我可以优化排除这些访问权限...”,可能会发生坏事)。


9

结构分配其中所有元素的总大小。

联合只会分配最大成员所需的内存。


2
您可能还希望添加工会成员,使其彼此“重叠”,因为它们都从分配的工会“结构”的起始地址开始。
Jim Buck

4

结构和联合有什么区别?

捷径的答案是:取决于内存分配。说明:在结构中,将为结构中的所有成员创建内存空间。在联合存储空间中,将仅为需要最大存储空间的成员创建空间。考虑以下代码:

struct s_tag
{
   int a; 
   long int b;
} x;

union u_tag
{
   int a; 
   long int b;
} y;

在struct和union中有两个成员:int和long int。int的内存空间为:4字节,long int的内存空间为:32位操作系统中的8。

因此,对于struct 4 + 8 = 12个字节,将创建8个字节的并集

代码示例:

#include<stdio.h>
struct s_tag
{
  int a;
  long int b;
} x;
union u_tag
{
     int a;
     long int b;
} y;
int main()
{
    printf("Memory allocation for structure = %d", sizeof(x));
    printf("\nMemory allocation for union = %d", sizeof(y));
    return 0;
}

参考:http : //www.codingpractise.com/home/c-programming/structure-and-union/


3

并集的使用当需要特殊类型的对话时,会经常使用并集。了解工会的用处。c / c标准库没有定义专门设计用于将短整数写入文件的函数。使用fwrite()会产生简单操作所需的过多开销。但是,使用联合可以轻松创建一个函数,该函数一次将一个短整数的二进制文件一次写入一个文件。我假设短整数是2个字节长

这个例子:

#include<stdio.h>
union pw {
short int i;
char ch[2];
};
int putw(short int num, FILE *fp);
int main (void)
{
FILE *fp;
fp fopen("test.tmp", "wb ");
putw(1000, fp); /* write the value 1000 as an integer*/
fclose(fp);
return 0;
}
int putw(short int num, FILE *fp)
{
pw word;
word.i = num;
putc(word.c[0] , fp);
return putc(word.c[1] , fp);
}    

尽管我用短整数调用了putw(),但是可以使用putc()和fwrite()。但我想举一个例子说明如何使用工会


3

结构是不同数据类型的集合,其中可以存储不同类型的数据,并且每个数据都有自己的内存块

当我们确保一次只使用一个变量并且您要充分利用当前内存时,我们通常使用并集,因为它仅获得一个等于最大类型的内存块。

struct emp
{
    char x;//1 byte
    float y; //4 byte
} e;

总内存=> 5字节

union emp
{
    char x;//1 byte
    float y; //4 byte
} e;

总内存= 4字节


2

在编写下面给出的字节排序函数时,并集非常方便。使用结构是不可能的。

int main(int argc, char **argv) {
    union {
        short   s;
        char    c[sizeof(short)];
    } un;

    un.s = 0x0102;

    if (sizeof(short) == 2) {
        if (un.c[0] == 1 && un.c[1] == 2)
            printf("big-endian\n");
        else if (un.c[0] == 2 && un.c[1] == 1)
            printf("little-endian\n");
        else
            printf("unknown\n");
    } else
        printf("sizeof(short) = %d\n", sizeof(short));

    exit(0);
}
// Program from Unix Network Programming Vol. 1 by Stevens.

1

联合与结构不同,因为联合在其他联合上重复进行:它重新定义了相同的内存,而该结构一个又一个地定义了另一个,没有重叠或重新定义。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.