C ++理解联盟和结构

时间:2014-10-23 06:41:55

标签: c++ struct unions

我正在开展一个正在进行的项目,其中一些工会的定义如下:

/* header.h */
typedef union my_union_t {
  float data[4];
  struct {
    float varA;
    float varB;
    float varC;
    float varD;
  };
} my_union;

如果我理解得很好,工会就是为了节省空间,所以sizeof(my_union_t)=其中变量的MAX。使用上面的陈述而不是这个陈述有什么好处:

typedef struct my_struct {
  float varA;
  float varB;
  float varC;
  float varD;
};

不会为这两个人分配的空间相同吗?

如何从 my_union 初始化varA,varB ...?

7 个答案:

答案 0 :(得分:2)

联盟主要不是为了节省空间,而是为了实施sum types(为此,您将union放在某些structclass中一个可以保留运行时标记的区别字段。另外,我建议您使用最近的C ++标准,至少C++11,因为它更好地支持联合(例如,允许更容易地联合对象及其构造或初始化)。

使用你的联合的好处是能够将n - 浮点(0 <= n <= 3)索引为u.data[n]

在声明为my_union u;的某个变量中指定一个联合字段,只需代码,例如u.varB = 3.14;在您的情况下与u.data[1] = 3.14;

具有相同的效果

当之无愧的联合的一个很好的例子是 mutable 对象,它可以包含intstring(在这种情况下你不能使用派生类):

class IntOrString {
   bool isint;
   union {
      int num; // when isint is true
      str::string str; // when isint is false
   };
 public:
   IntOrString(int n=0) : isint(true), num(n) {};
   IntOrString(std::string s) : isint(false), str(s) {};
   IntOrString(const IntOrString& o): isint(o.isint) 
      { if (isint) num = o.num; else str = o.str); };
   IntOrString(IntOrString&&p) : isint(p.isint) 
      { if (isint) num = std::move (p.num); 
        else str = std::move (p.str); };
   ~IntOrString() { if (isint) num=0; else str->~std::string(); }; 
   void set (int n) 
     { if (!isint) str->~std::string(); isint=true; num=n; };
   void set (std::string s) { str = s; isint=false; };
   bool is_int() const { return isint; };
   int as_int() const { return (isint?num:0; };
   const std::string as_string() const { return (isint?"":str;};
 }; 

注意显式调用str字段的析构函数。另请注意,您可以在标准容器(IntOrString

中安全地使用std::vector<IntOrString>

在C ++的未来版本中也可以参见std::optional(概念上是与void标记的联合)

BTW,在Ocaml中,您只需编码:

 type intorstring = Integer of int | String of string;;

并且您将使用pattern matching。如果你想让它变得可变,你需要做一个记录或参考它。

您最好以C ++惯用方式使用union - s(有关一般建议,请参阅this)。

答案 1 :(得分:2)

在实现类似对象(类型字段和数据类型的并集)的变体或实现序列化时经常使用联合。

你使用工会的方式是灾难的一种方法。

您假设struct中的union正在打包float,之间没有差距! 该标准保证float data[4];是连续的,但不保证结构元素。你知道的另一件事就是varA的地址;与data[0]的地址相同。

绝不以这种方式使用联盟。

关于你的问题:&#34;我怎样才能从my_union初始化varA,varB ...&#34;。答案是,通过data[]数组以正常的冗长方式访问结构成员而不是

答案 2 :(得分:1)

优点是,使用联合,您可以通过两种不同的方式访问相同的内存。

在您的示例中,union包含四个浮点数。您可以将这些浮点数作为varA,varB ...访问,这可能是更具描述性的名称,或者您可以访问与数组数据[0],数据[1]相同的变量...这可能在循环中更有用。

使用联合,您也可以将相同的内存用于不同类型的数据,您可能会发现这对于编写函数来说是有用的,可以告诉您是否在大端或小端CPU上。

答案 3 :(得分:1)

不,这不是为了节省空间。它能够将一些二进制数据表示为各种数据类型。 例如

#include <iostream>
#include <stdint.h>

union Foo{
   int x;
   struct y
   {
      unsigned char b0, b1, b2, b3;
   };
   char z[sizeof(int)];
};

int main()
{
   Foo bar;
   bar.x = 100;

   std::cout << std::hex; // to show number in hexadec repr;
   for(size_t i = 0; i < sizeof(int); i++)
   {
      std::cout << "0x" << (int)bar.z[i] << " "; // int is just to show values as numbers, not a characters
   }

   return 0;
}

输出:0x64 0x0 0x0 0x0相同的值存储在struct bar.y中,但不存储在数组中,而是存储在sturcture成员中。这是因为我的机器有一点endiannes。如果它很大,那么输出就会反转:0x0 0x0 0x0 0x64

您可以使用reinterpret_cast

实现相同目标
#include <iostream>
#include <stdint.h>

int main()
{

   int x = 100;
   char * xBytes = reinterpret_cast<char*>(&x);

   std::cout << std::hex; // to show number in hexadec repr;
   for (size_t i = 0; i < sizeof(int); i++)
   {
      std::cout << "0x" << (int)xBytes[i] << " "; // (int) is just to show values as numbers, not a characters
   }

   return 0;
}

它很有用,例如,当你需要读取一些二进制文件时,它写在一个与你的结尾不同的机器上。您只需将值作为bytearray访问,并根据需要交换这些字节。

此外,当你必须处理bit fields时它是有用的,但它是一个完全不同的故事:)

答案 4 :(得分:1)

我认为理解工会的最好方法就是给出两个常见的实例。

第一个例子是使用图像。想象一下,你有和RGB图像排列在一个长缓冲区 大多数人会做的是将缓冲区表示为char*,然后将其循环3以获得R,G,B。

你可以做的是做一个小联合,然后使用它来遍历图像缓冲区:

union RGB
{
   char raw[3];
   struct 
   {
      char R;
      char G;
      char B;
   } colors;
}

RGB* pixel = buffer[0];
///pixel.colors.R == The red color in the first pixel.

工会的另一个非常有用的用途是使用寄存器和位域 假设你有一个32位的值,代表一些HW寄存器,或者其他东西 有时,为了节省空间,您可以将32位分成位字段,但您还希望该寄存器的整个表示形式为32位类型。
这显然可以节省许多程序员毫无理由地使用的位移计算。

union MySpecialRegister
{
    uint32_t register;
    struct 
    {
       unsigned int firstField           : 5;
       unsigned int somethingInTheMiddle : 25;
       unsigned int lastField            : 6; 
    } data;
}
// Now you can read the raw register into the register field
// then you can read the fields using the inner data struct

答案 5 :(得分:0)

联盟主要用于以不同方式表示相同的数据。想象一下,如果您有一个包含各种数据的用户定义结构,例如:

typedef struct
{
  int x;
  int y;
  float f;
} my_struct;

现在您想通过串行总线将此结构发送到另一台计算机。但是您的串行总线硬件一次只能发送1个字节。在接收器端,有一个x字节的硬件缓冲区。为了能够发送数据类型,您可以建立联合:

typedef union
{ 
  my_struct s;
  uint8_t   bytes[sizeof(my_struct)];
} my_union;

现在,只要您想发送和接收,就可以使用联合的bytes成员;当您想要访问实际数据时,可以使用s成员。

上述概念在硬件相关编程中经常使用:数据协议,硬件寄存器定义,内存映射,NVM驱动程序等。

注意:每当你做这样的事情时,要小心填充和对齐!编译器可以在struct / union内的任何位置自由插入填充字节以对齐数据。这可以打破上述概念。

正如其他答案中所指出的,你也可以使用工会来实现&#34;和类型&#34; /&#34;变种&#34;,但是这种用途的实际用途非常有限,如果有这样的用途甚至存在。

答案 6 :(得分:-1)

首先:避免访问访问相同内存但不同类型的联盟!

工会根本没有节省空间。唯一在同一内存区域定义多个名称!并且您只能在联合中一次存储其中一个元素。

如果你有

union X
    {
        int x;
        char y[4];
     };

你可以存储一个int OR 4个字符,但不能同时存储两个字符!一般的问题是,没有人知道哪个数据实际存储在一个联合中。如果存储一个int并读取字符,编译器将不会检查它,也没有运行时检查。解决方案通常是在结构中为union提供一个额外的数据元素,它包含实际存储的数据类型作为枚举。

 struct Y
 {
      enum { IS_CHAR, IS_INT } tinfo;
      union
      {
           int x;
           char y[4];
       };
  }

但是在c ++中你总是应该使用类或结构,它们可以从像这样的空父类派生出来:

class Base
{
};

class Int_Type: public Base
{
    ...
    int x;
};

class Char_Type: public Base
{
     ...
    char y[4];
};

所以你可以设置指向base的指针,它实际上可以为你保存Int或Char类型。使用虚函数,您可以以面向对象的编程方式访问成员。

正如Basile的回答所提到的,一个有用的案例可以是通过不同名称访问相同类型

union X 
{
      struct data
      {
          float a;
          float b;
      };
      float arr[2];
 };

允许使用与相同类型相同数据的不同访问方式。应该避免使用存储在同一存储器中的不同类型!