什么时候应该使用工会?我们为什么需要它们?


当前回答

联合在嵌入式编程或需要直接访问硬件/内存的情况下特别有用。这里有一个简单的例子:

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;

然后,您可以按如下方式访问reg:

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;

字节顺序和处理器体系结构当然很重要。

另一个有用的特性是位修饰符:

typedef union
{
    struct {
        unsigned char b1:1;
        unsigned char b2:1;
        unsigned char b3:1;
        unsigned char b4:1;
        unsigned char reserved:4;
    } bits;
    unsigned char byte;
} HW_RegisterB;
HW_RegisterB reg;

使用这段代码,您可以直接访问寄存器/内存地址中的单个位:

x = reg.bits.b2;

其他回答

联合通常用于整数和浮点数的二进制表示之间的转换:

union
{
  int i;
  float f;
} u;

// Convert floating-point bits to integer:
u.f = 3.14159f;
printf("As integer: %08x\n", u.i);

尽管根据C标准,这在技术上是未定义的行为(您只应该阅读最近编写的字段),但它将在几乎任何编译器中以定义良好的方式起作用。

联合有时也被用来实现C语言中的伪多态性,通过给一个结构一些标记来指示它包含什么类型的对象,然后将可能的类型联合在一起:

enum Type { INTS, FLOATS, DOUBLE };
struct S
{
  Type s_type;
  union
  {
    int s_ints[2];
    float s_floats[2];
    double s_double;
  };
};

void do_something(struct S *s)
{
  switch(s->s_type)
  {
    case INTS:  // do something with s->s_ints
      break;

    case FLOATS:  // do something with s->s_floats
      break;

    case DOUBLE:  // do something with s->s_double
      break;
  }
}

这使得struct S的大小只有12字节,而不是28字节。

许多答案都涉及从一种类型转换到另一种类型。我从具有相同类型的联合中得到最多的使用(即在解析串行数据流时)。它们允许解析/构造一个有框架的包变得很简单。

typedef union
{
    UINT8 buffer[PACKET_SIZE]; // Where the packet size is large enough for
                               // the entire set of fields (including the payload)

    struct
    {
        UINT8 size;
        UINT8 cmd;
        UINT8 payload[PAYLOAD_SIZE];
        UINT8 crc;
    } fields;

}PACKET_T;

// This should be called every time a new byte of data is ready 
// and point to the packet's buffer:
// packet_builder(packet.buffer, new_data);

void packet_builder(UINT8* buffer, UINT8 data)
{
    static UINT8 received_bytes = 0;

    // All range checking etc removed for brevity

    buffer[received_bytes] = data;
    received_bytes++;

    // Using the struc only way adds lots of logic that relates "byte 0" to size
    // "byte 1" to cmd, etc...
}

void packet_handler(PACKET_T* packet)
{
    // Process the fields in a readable manner
    if(packet->fields.size > TOO_BIG)
    {
        // handle error...
    }

    if(packet->fields.cmd == CMD_X)
    {
        // do stuff..
    }
}

编辑 关于字节序和结构填充的评论是有效的,而且非常值得关注。我几乎完全在嵌入式软件中使用了这段代码,其中大部分我都可以控制管道的两端。

包含不同记录类型的文件。 包含不同请求类型的网络接口。

看一下这个:X.25缓冲区命令处理

许多可能的X.25命令中的一个被接收到缓冲区中,并通过使用所有可能结构的UNION进行适当的处理。

在学校里,我是这样使用联合的:

typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

我用它来更容易地处理颜色,而不是使用>>和<<操作符,我只需要遍历我的char数组的不同索引。

当你有一个函数,你返回的值可以不同,这取决于函数做了什么,使用联合。