【问题标题】:Advantages of using union when same thing can be done using struct - C当可以使用 struct - C 完成相同的事情时使用联合的优势
【发布时间】:2010-07-28 11:02:48
【问题描述】:

我很难理解 union 在 C 中的使用。我在这里阅读了很多关于这个主题的帖子。但是他们都没有解释为什么union 在使用结构可以实现相同的事情时是首选。

引自 K&R

作为可能找到的示例 在编译器符号表管理器中, 假设一个常数可能是一个 int, 浮点数或字符指针。这 特定常数的值必须是 存储在适当的变量中 类型,但它最方便 如果值占用,则表管理 相同的存储量并且是 存储在同一个地方,无论 它的类型。这是一个目的 联合一个单一的变量,可以 合法持有其中任何一项 几种类型。语法基于 结构:

union u_tag {
      int ival;
      float fval;
      char *sval;
} u;

用法会是

if (utype == INT)
    printf("%d\n", u.ival);
if (utype == FLOAT)
    printf("%f\n", u.fval);
if (utype == STRING)
    printf("%s\n", u.sval);
else
    printf("bad type %d in utype\n", utype);

同样的事情可以使用结构来实现。有点像,

struct u_tag {
    utype_t utype;
    int ival;
    float fval;
    char *sval;
} u;

if (u.utype == INT)
    printf("%d\n", u.ival);
if (u.utype == FLOAT)
    printf("%f\n", u.fval);
if (u.utype == STRING)
    printf("%s\n", u.sval);
else
    printf("bad type %d in utype\n", utype);

这不一样吗? union 有什么优势?

有什么想法吗?

【问题讨论】:

    标签: c data-structures struct unions


    【解决方案1】:

    在您发布的示例中, union 的大小将是 float 的大小(假设它是最大的 - 正如 cmets 中指出的那样,它在 64 位编译器中可能会有所不同),而 struct 的大小会是 float、int、char* 和 utype_t(以及填充,如果有的话)的大小之和。

    我的编译器上的结果:

    union u_tag {
        int ival;
        float fval;
        char *sval;
    };
    struct s_tag {
        int ival;
        float fval;
        char *sval;
    };
    
    int main()
    {
        printf("%d\n", sizeof(union u_tag));  //prints 4
        printf("%d\n", sizeof(struct s_tag)); //prints 12
        return 0;
    }
    

    【讨论】:

    • 谢谢。所以唯一的区别是大小?
    • 如果你在 64 位平台上,那么 union 的大小就是 char* 的大小。
    • @Appu 以及易用性:struct 在大多数情况下都很方便。仅在必要时使用联合。
    • @Eric ya,这就是为什么我说“我的编译器上的结果”
    • @Appu - 不,它们根本不同,一个结构体分别存储所有值。联合允许您将单个内存位置称为 int/float/long 等。
    【解决方案2】:

    当一次需要访问的成员不超过一个时,可以使用联合。这样,您可以节省一些内存而不是使用结构。

    联合可能有一个巧妙的“欺骗”:写入一个字段并从另一个字段读取,以检查位模式或以不同方式解释它们。

    【讨论】:

    • 你也可以在 C 的弱类型系统中进行强制转换
    • +1 提到除了节省内存之外还使用了联合。
    • @Gary:是的,但是每次你想要进行切换时都需要进行演员表。在某些情况下使用联合可能更具可读性(当然在其他情况下可读性较差)。我看到的联合的一个常见情况是提供一种类型,该类型具有高部分和低部分(即,因为该类型大于本机类型所支持的大小),以使库之间的通信不需要新的类型。
    【解决方案3】:

    Union 使用更少的内存,让您做更多危险的事情。它表示一个连续的内存块,可以解释为整数、浮点值或字符指针。

    【讨论】:

      【解决方案4】:

      联合用于一次只保存一种类型的数据。如果重新分配一个值,则旧值将被覆盖并且无法访问。 在您的示例中, int 、float 和 char 成员在用作结构时可以随时具有不同的值。它不是工会的情况。 所以这取决于你的程序要求和设计。 检查此article 何时使用联合。 Google 可能会提供更多结果。

      【讨论】:

        【解决方案5】:

        该语言为程序员提供了许多工具,可以将高级抽象应用于最低级别的机器数据和操作。

        但是,仅仅存在某些东西并不能自动表明它的使用是最佳做法。它们的存在使语言强大而灵活。但是行业需求导致了编程技术的发展,这些技术更倾向于清晰性和可维护性,而不是绝对最佳的代码效率或存储效率。

        因此,如果问题的解决方案集同时包含联合和结构,则程序员有责任确定对紧凑存储的需求是否超过成本。

        最近,内存成本非常低。 bool 类型(甚至更早于 int 变量)的引入允许 32 位系统的程序员使用 32 位来表示二进制状态。即使程序员可以使用掩码并将 32 个真/假值放入一个变量中,您也经常会在编程中看到这一点。

        因此,为了回答您的问题,与传统结构相比,联合为多种可能类型中的单个值实体提供了更紧凑的存储,但代价是清晰度和可能存在的细微程序缺陷。

        【讨论】:

          【解决方案6】:

          在现代系统中大多数情况下不会使用联合来节省内存,因为访问联合成员的代码将很快占用更多空间(并且速度更慢),而不是仅仅将另一个字大小的变量添加到内存中。但是,当您的代码必须支持具有不同字节顺序的多种架构时(哇,多么棒的词),联合可能会很方便。我倾向于使用端序实用程序库(to 函数),但有些人喜欢联合。

          内存映射硬件寄存器也通常使用联合访问。 C 中的位字段(不要使用它们,它们很卑鄙)可以使用联合作为单词传递。

          【讨论】:

            【解决方案7】:

            联合有两个主要用途:

            首先是提供一个变体类型,正如您所概述的。与 struct 方法相比,联合中的所有成员之间共享一个内存单元。如果内存不是问题,结构也将提供此功能。

            我通常将联合嵌入到结构中 - 结构确保类型和数据存储在一起,联合意味着只存储一个值。

            struct any_tag {
                utype_t utype;
                union {
                    int ival;
                    float fval;
                    char *sval;
                } u;
            } data;
            

            其次,联合对原始数据的低级访问非常有用——将一种类型重新解释为另一种类型。我使用它的目的是读取和写入二进制编码数据。

            float ConvertByteOrderedBufferTo32bitFloat( char* input ) {
            union {
                float f;
                unsigned char buf[4];
            } data;
            
            #if WORDS_BIGENDIAN == 1
            data.buf[0] = input[0];
            data.buf[1] = input[1];
            data.buf[2] = input[2];
            data.buf[3] = input[3];
            #else
            data.buf[0] = input[3];
            data.buf[1] = input[2];
            data.buf[2] = input[1];
            data.buf[3] = input[0];
            #endif
            
            return dat1.f;
            }
            

            在这里,您可以写入单个字节,具体取决于平台字节序,然后将这 4 个原始 char 字节解释为 IEEE 浮点数。将该 char 数组转换为浮点数不会有相同的结果。

            【讨论】:

              【解决方案8】:

              如前所述:联合节省内存。 但这不是唯一的区别。 结构用于保存所有给定的子类型,而联合则用于保存给定子类型中的一个。因此,如果您想存储整数或浮点数,那么联合可能是您需要的东西(但您需要在其他地方记住您保存了哪种数字)。如果你想同时存储两者,那么你需要一个结构体。

              【讨论】:

                【解决方案9】:

                借用您一次发布的“......任何一种几种类型......”的工会成员。这正是 union 的含义。而结构成员都可以一次分配和访问。

                union 在执行一些系统级(os)程序(如进程通信/并发处理)时更有意义。

                【讨论】:

                  【解决方案10】:

                  工会很棘手。多年来,我无法弄清楚它们,然后我开始用网络协议做事,有人向我展示了光明。假设您有一个标头,然后在标头之后,有各种不同类型的数据包,例如:

                  |类型(4 字节) | uid (8 字节) |有效载荷长度(2 个字节) |有效载荷(可变长度)|

                  然后会有各种类型的数据包负载......为了争论,可能有你好,再见和消息包......

                  好吧,您可以构建一组嵌套的结构/联合,它们可以像这样准确地表示该协议中的数据包......

                  struct packet {
                    uint type;
                    char unique_id [8];
                    ushort payload_length;
                    union payload {
                  
                      struct hello {
                        ushort version;
                        uint status;
                      };
                  
                      struct goodbye {
                        char reason[20];
                        uint status;
                      };
                  
                      struct message {
                        char message[100];
                      };
                  
                    };
                  };
                  

                  不可避免地,您会通过 read() 调用从操作系统获取此协议,而这只是一堆乱七八糟的字节。但是如果你对你的结构定义很小心,并且所有类型的大小都是正确的,你可以简单地创建一个指向结构的指针,将它指向你填充随机数据的缓冲区,然后......

                  char buf[100];
                  packet *pkt;
                  read(outsideworld,&buf,1000);
                  pkt = (struct packet *)&buf;
                  

                  读取你的数据包就这么简单......

                  switch(pkt->type){
                  
                    case PACKET_MESSAGE:
                      printf("message = %s\n",
                             pkt->payload.message.message);
                      break;
                  
                    case PACKET_HELLO:
                      printf("hello! version = %d status = %d\n",
                             pkt->payload.hello.version,
                             pkt->payload.hello.status);
                      break;
                    case PACKET_GOODBYE:
                      printf("goodbye! reason = %s status = %d\n",
                             pkt->payload.goodbye.reason,
                             pkt->payload.goodbye.status);
                      break;
                  }
                  

                  没有卑躬屈膝,计算字节等...您可以根据需要将其嵌套得尽可能深(为 ip 地址创建一个联合,将整个内容作为无符号整数或单个字节,因此更容易打印 192.168.0.1)。

                  联合不会减慢您的代码速度,因为它只是被转换为机器代码中的偏移量。

                  【讨论】:

                    【解决方案11】:

                    这里有一个例子。请看下面的例子:

                    union xReg
                    {
                        uint allX;
                        struct
                        {
                            uint x3      : 9;
                            uint x2      : 9;
                            uint x1      : 14;
                        };
                    };
                    

                    uint 是 unsigned int 的 typedef。

                    这里,这个联合代表一个 32 位的寄存器。您可以使用 allX 读取寄存器,然后使用 struct 对其进行操作。

                    如果我们使用 allX 进行位操作,这可以避免不必要的位移。

                    【讨论】:

                      猜你喜欢
                      • 2015-01-10
                      • 2011-09-25
                      • 2016-04-26
                      • 2020-12-20
                      • 1970-01-01
                      • 2011-09-22
                      • 1970-01-01
                      • 2023-03-22
                      • 2011-03-14
                      相关资源
                      最近更新 更多