为什么我们需要C联盟?

时间:2008-10-31 03:51:41

标签: c unions

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

19 个答案:

答案 0 :(得分:237)

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

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个。

答案 1 :(得分:123)

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

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;

答案 2 :(得分:59)

低级系统编程是一个合理的例子。

IIRC,我使用了工会将硬件寄存器分解为组件位。因此,您可以访问一个8位寄存器(就像我在那里做的那样;-)到组件位。

(我忘记了确切的语法但是......)这种结构允许控制寄存器作为control_byte或通过各个位进行访问。确保位映射到给定字节序的正确寄存器位是很重要的。

typedef union {
    unsigned char control_byte;
    struct {
        unsigned int nibble  : 4;
        unsigned int nmi     : 1;
        unsigned int enabled : 1;
        unsigned int fired   : 1;
        unsigned int control : 1;
    };
} ControlRegister;

答案 3 :(得分:33)

我在几个库中看到它作为面向对象继承的替代。

E.g。

        Connection
     /       |       \
  Network   USB     VirtualConnection

如果您希望Connection“class”是上述任何一种,您可以编写如下内容:

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};

在libinfinity中使用示例:http://git.0x539.de/?p=infinote.git;a=blob;f=libinfinity/common/inf-session.c;h=3e887f0d63bd754c6b5ec232948027cbbf4d61fc;hb=HEAD#l74

答案 4 :(得分:29)

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

在以下示例中:

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

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

答案 5 :(得分:25)

很多用法。只需grep union /usr/include/*或类似的目录。大多数情况union都包含在struct中,结构的一个成员告诉联合中哪个元素可以访问。例如,结帐man elf用于实际实现。

这是基本原则:

struct _mydata {
    int which_one;
    union _data {
            int a;
            float b;
            char c;
    } foo;
} bar;

switch (bar.which_one)
{
   case INTEGER  :  /* access bar.foo.a;*/ break;
   case FLOATING :  /* access bar.foo.b;*/ break;
   case CHARACTER:  /* access bar.foo.c;*/ break;
}

答案 6 :(得分:17)

这是一个来自我自己的代码库的联盟的例子(来自内存和转述所以它可能不准确)。它用于在我构建的解释器中存储语言元素。例如,以下代码:

set a to b times 7.

由以下语言元素组成:

  • 符号[组]
  • 变量[α]
  • 符号[到]
  • 变量并[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;

要创建“variable [b]”元素,您可以使用:

tElem e;
e.typ = ELEM_VARIABLE;
e.str = strdup ("b");   // make sure you free this later

要创建“常量[7]”元素,您可以使用:

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;

您可以轻松地将其展开以包含浮点数(float flt)或有理数(struct ratnl {int num; int denom;})和其他类型。

基本前提是strval在内存中不是连续的,它们实际上是重叠的,因此它是一种在同一块内存上获得不同视图的方法,如图所示,其中结构基于内存位置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 |       |
       +-------+

答案 7 :(得分:7)

我认为这样可以更容易地重用可能以不同方式使用的内存,即节省内存。例如。你想做一些能够保存短字符串和数字的“变体”结构:

struct variant {
    int type;
    double number;
    char *string;
};

在32位系统中,这将导致variant的每个实例使用至少96位或12个字节。

使用联合可以将大小减小到64位或8个字节:

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};

如果你想添加更多不同的变量类型等,你可以节省更多。可能是真的,你可以做类似的事情投射一个空指针 - 但是联盟使得它更容易被访问以及类型安全。这样的节省听起来不是很大,但是你节省了三分之一用于这个结构的所有实例的内存。

答案 8 :(得分:5)

当你需要这种类型的灵活结构时,很难想到一个特定的场合,也许是在你要发送不同大小的消息的消息协议中,但即使这样,也可能有更好的程序员友好的替代方案。

Unions有点像其他语言中的变体类型 - 它们一次只能容纳一个东西,但是这个东西可能是int,float等等,这取决于你如何声明它。

例如:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};

MyUnion只会包含一个int OR a float,取决于你最近设置的。所以这样做:

MYUNION u;
u.MyInt = 10;

你现在拥有一个等于10的int;

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

联合的大小取决于其最大字段的大小,在本例中为浮点数。

答案 9 :(得分:4)

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

答案 10 :(得分:3)

这些答案中的许多都涉及从一种类型到另一种类型的转换。我从具有相同类型的联合中获得最多的用途(即解析串行数据流时)。它们允许解析/构造框架数据包变得微不足道。

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

修改 的 关于字节序和结构填充的注释是有效的,也是很好的关注点。我几乎完全在嵌入式软件中使用了这个代码体,其中大部分都控制了管道的两端。

答案 11 :(得分:1)

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

看看这个:X.25 buffer command handling

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

答案 12 :(得分:1)

在C的早期版本中,所有结构声明都将共享一组通用字段。给出:

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

编译器本质上会生成一个结构大小(可能是对齐)的表,以及一个结构'成员'名称,类型和偏移的单独表。编译器没有跟踪哪些成员属于哪些结构,并且只有在类型和偏移量匹配时才允许两个结构具有相同名称的成员(与q的成员struct x一样和struct y)。如果p是指向任何结构类型的指针,则p-> q会将“q”的偏移量添加到指针p并从结果地址中获取“int”。

鉴于上述语义,可以编写一个可以互换地对多种结构执行一些有用操作的函数,前提是函数使用的所有字段都与所讨论的结构中的有用字段对齐。这是一个有用的功能,并且更改C以验证用于结构访问的成员对相关结构的类型将意味着在没有具有可以在同一地址处包含多个命名字段的结构的方式时丢失它。将“联合”类型添加到C有助于填补这一空白(尽管不是,恕我直言,以及应该如此)。

工会填补这一空白的能力的一个重要部分是,指向工会成员的指针可以转换为指向包含该成员的任何联合的指针,并且指向任何联合的指针可以转换为指向任何成员。虽然C89标准没有明确表示直接向T*投射U*等同于将其转换为指向包含TU的任何联合类型的指针,然后将其转换为U*,后一个强制转换序列的定义行为将不会受到所使用的联合类型的影响,并且标准没有为从TU的直接强制转换指定任何相反的语义T*。此外,在函数接收到未知来源的指针的情况下,通过T*编写对象,将U*转换为U*,然后通过{{读取对象的行为1}}等同于通过类型T的成员编写联合并读取类型U,这在少数情况下是标准定义的(例如,当访问公共初始序列成员时)和实现 - 其余的定义(而不是未定义)。尽管程序很少使用联合类型的实际对象来利用CIS保证,但更常见的是利用指向未知来源对象的指针必须表现得像联盟成员的指针并具有与之相关的行为保证这一事实。

答案 13 :(得分:1)

在学校里,我使用过这样的工会:

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

我用它来更轻松地处理颜色,而不是使用>>和<<运算符,我只需要浏览我的char数组的不同索引。

答案 14 :(得分:1)

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

union data {
    int data;
    struct {
        unsigned char higher;
        unsigned char lower;
    } parts;
};

它不需要移位,因此代码更容易阅读。

另一方面,我看到一些使用union for stl allocator的旧C ++ stl代码。如果您有兴趣,可以阅读sgi stl源代码。这是它的一部分:

union _Obj {
    union _Obj* _M_free_list_link;
    char _M_client_data[1];    /* The client sees this.        */
};

答案 15 :(得分:1)

COM接口中使用的VARIANT怎么样?它有两个字段 - “type”和一个包含实际值的union,它根据“type”字段进行处理。

答案 16 :(得分:1)

工会很棒。我见过的一个巧妙使用的工会是在定义事件时使用它们。例如,您可能认为事件是32位。

现在,在32位内,您可能希望将前8位指定为事件发送者的标识符...有时您会处理整个事件,有时您会剖析它并比较它的组件。工会让你可以灵活地做到这两点。

union Event
{
  unsigned long eventCode;
  unsigned char eventParts[4];
};

答案 17 :(得分:0)

一个简单且非常有用的例子是....

想象:

你有一个uint32_t array[2]并想要访问Byte链的第3和第4个字节。 你可以做*((uint16_t*) &array[1])。 但这可悲地打破了严格的走样规则!

但是已知的编译器允许您执行以下操作:

union un
{
    uint16_t array16[4];
    uint32_t array32[2];
}
从技术上讲,这仍然违反了规则。但是所有已知的标准都支持这种用法。

答案 18 :(得分:-1)

当您有某个函数返回的值可能因函数的作用而异时,请使用联合。