为什么我们需要C工会?

为什么我们需要C工会?,c,unions,C,Unions,什么时候应该使用工会?为什么我们需要它们?联合允许相互排斥的数据成员共享相同的内存。当内存更为稀缺时,这一点非常重要,例如在嵌入式系统中 在以下示例中: union { int a; int b; int c; } myUnion; 此并集将占用单个int的空间,而不是3个单独的int值。如果用户将值设置为a,然后将值设置为b,则会覆盖a的值,因为它们共享相同的内存位置。当您要对硬件、设备或网络协议定义的结构建模时,会使用联合,或者当您创建大量对象并希望节省空间时。不过,95

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

联合允许相互排斥的数据成员共享相同的内存。当内存更为稀缺时,这一点非常重要,例如在嵌入式系统中

在以下示例中:

union {
   int a;
   int b;
   int c;
} myUnion;

此并集将占用单个int的空间,而不是3个单独的int值。如果用户将值设置为a,然后将值设置为b,则会覆盖a的值,因为它们共享相同的内存位置。

当您要对硬件、设备或网络协议定义的结构建模时,会使用联合,或者当您创建大量对象并希望节省空间时。不过,95%的时候你确实不需要它们,请坚持使用易于调试的代码。

通常使用并集在整数和浮点的二进制表示形式之间进行转换:

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;
  }
}

这使得结构的大小仅为12字节,而不是28字节。

很难想象在特定情况下需要这种灵活的结构,也许在消息协议中,您将发送不同大小的消息,但即便如此,可能还有更好、更方便程序员的替代方案

联合有点像其他语言中的变体类型——它们一次只能保存一个对象,但该对象可以是int、float等,具体取决于您如何声明它

例如:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};
MyUnion将仅包含int或float,具体取决于您最近设置的值。这样做:

MYUNION u;
u.MyInt = 10;
u现在拥有一个等于10的整数

u.MyFloat = 1.0;
u现在持有等于1.0的浮点值。它不再保存int。显然,现在如果您尝试执行printf(“MyInt=%d”,u.MyInt);然后你可能会得到一个错误,尽管我不确定具体的行为


联合体的大小由其最大字段的大小决定,在本例中为浮点。

联合体非常好。我见过的联合的一个巧妙用法是在定义事件时使用它们。例如,您可以确定事件是32位的

现在,在这32位中,您可能希望将前8位指定为事件发送方的标识符。。。有时您将事件作为一个整体来处理,有时您将其分解并比较其组件。工会让你可以灵活地同时做这两件事

union Event { unsigned long eventCode; unsigned char eventParts[4]; }; 工会活动 { 无符号长事件码; 无符号字符事件部分[4]; };
下面是一个来自我自己的代码库的联合示例(来自内存并进行了解释,因此可能不准确)。它被用来在我构建的解释器中存储语言元素。例如,以下代码:

set a to b times 7.
由以下语言元素组成:

  • 符号[集]
  • 变量[a]
  • 符号[至]
  • 变量[b]
  • 符号[次]
  • 常数[7]
  • 符号[.]
语言元素被定义为“
#define
”值,因此:

#define ELEM_SYM_SET        0
#define ELEM_SYM_TO         1
#define ELEM_SYM_TIMES      2
#define ELEM_SYM_FULLSTOP   3
#define ELEM_VARIABLE     100
#define ELEM_CONSTANT     101
以下结构用于存储每个元素:

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;
然后,每个元素的大小是最大联合的大小(对于类型为4字节,对于联合为4字节,尽管这些是典型值,但实际大小取决于实现)

为了创建“set”元素,您将使用:

tElem e;
e.typ = ELEM_SYM_SET;
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
为了创建“变量[b]”元素,您将使用:

tElem e;
e.typ = ELEM_SYM_SET;
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
为了创建“常量[7]”元素,您可以使用:

tElem e;
e.typ = ELEM_SYM_SET;
tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later
tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
您可以轻松地将其扩展为包括浮点(
float flt
)或有理数(
struct ratnl{int num;int denom;}
)和其他类型

基本前提是
str
val
在内存中不是连续的,它们实际上是重叠的,因此这是一种在相同内存块上获得不同视图的方法,如图所示,其中结构基于内存位置
0x1010
,整数和指针都是4字节:

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+
如果它只是在一个结构中,它会像这样:

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+
typedef union
{
  unsigned char color[4];
  int       new_color;
}       u_color;

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

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.dword = 0x12345678;
reg.bytes.byte3 = 4;
Endianness(字节顺序)和处理器架构当然很重要

另一个有用的功能是位修饰符:

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;

在COM接口中使用的是什么?它有两个字段——“type”和一个union,其中包含一个实际值,该值根据“type”字段进行处理。

我在为嵌入式设备编码时使用union。我有16位长的C int。当我需要读取/存储EEPROM时,我需要检索较高的8位和较低的8位。所以我用这种方式:

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};
它不需要移位,因此代码更容易阅读

另一方面,我看到了一些用C++作为STL分配器的C++ STL代码。如果您感兴趣,可以阅读源代码。以下是其中的一部分:

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};
  • 包含不同记录类型的文件
  • 包含不同请求类型的网络接口
看看这个:


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

我想说,这样可以更容易地重用可能以不同方式使用的内存,即节省内存。例如,您希望使用能够保存短字符串和数字的“variant”结构:

struct variant {
    int type;
    double number;
    char *string;
};
在32位系统中,这将导致至少96位或1位
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..
    }
}
struct x {int x_mode; int q; float x_f};
struct y {int y_mode; int q; int y_l};
struct z {int z_mode; char name[20];};