为什么我们需要C联盟?

222 c unions

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

Ada*_*eld 242

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

union
{
  int i;
  float f;
} u;

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

虽然根据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;
  }
}
Run Code Online (Sandbox Code Playgroud)

这允许大小struct S只有12个字节,而不是28个字节.

  • 我觉得应该删除关于未定义行为的免责声明。事实上,它是定义的行为。请参阅 C99 标准的脚注 82: *如果用于访问联合对象内容的成员与上次用于在对象中存储值的成员不同,则重新解释该值的对象表示的适当部分作为 6.2.6 中描述的新类型中的对象表示(有时称为“类型双关”的过程)。这可能是一个陷阱表示。* (4认同)
  • @spin_eight:它不是从float"转换"到int.更像是"重新解释float的二进制表示,就好像它是一个int".输出不是3:http://ideone.com/MKjwon我不知道为什么Adam打印为十六进制. (3认同)

kgi*_*kis 127

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

typedef union
{
    struct {
        unsigned char byte1;
        unsigned char byte2;
        unsigned char byte3;
        unsigned char byte4;
    } bytes;
    unsigned int dword;
} HW_Register;
HW_Register reg;
Run Code Online (Sandbox Code Playgroud)

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

reg.dword = 0x12345678;
reg.bytes.byte3 = 4;
Run Code Online (Sandbox Code Playgroud)

字节顺序(字节顺序)和处理器架构当然很重要.

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

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;
Run Code Online (Sandbox Code Playgroud)

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

x = reg.bits.b2;
Run Code Online (Sandbox Code Playgroud)

  • 您在这里的答案与@Adam Rosenfield的上述答案一起构成了完美的互补对:您演示在联合中使用**结构**,而他演示在结构中使用** **。事实证明,我需要同时执行两个操作:一个**结构中的并集内部的**结构,以便在嵌入式系统上的线程之间在C中实现一些奇特的消息传递多态性,而且我不会意识到两个答案都在一起。 (2认同)
  • 为什么以“b1”而不是“b0”开头?问题是没有有关订单的信息。在您的示例中,“b1”可以是位 0 或最高位(可能是位 7)。 (2认同)

Sni*_*ips 63

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

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;
Run Code Online (Sandbox Code Playgroud)

  • 这是一个很好的例子!以下是如何在嵌入式软件中使用此技术的示例:http://www.edn.com/design/integrated-circuit-design/4394915/Managing-the-8--to-32-bit-processor-移民 (3认同)

bb-*_*ion 34

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

例如

        Connection
     /       |       \
  Network   USB     VirtualConnection
Run Code Online (Sandbox Code Playgroud)

如果你想让Connection"class"成为上述任何一个,你可以这样写:

struct Connection
{
    int type;
    union
    {
        struct Network network;
        struct USB usb;
        struct Virtual virtual;
    }
};
Run Code Online (Sandbox Code Playgroud)

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


Leo*_*Hat 29

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

在以下示例中:

union {
   int a;
   int b;
   int c;
} myUnion;
Run Code Online (Sandbox Code Playgroud)

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


pho*_*xis 26

很多用法.只是做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;
}
Run Code Online (Sandbox Code Playgroud)


pax*_*blo 17

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

set a to b times 7.
Run Code Online (Sandbox Code Playgroud)

由以下语言元素组成:

  • 符号[组]
  • 变量并[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
Run Code Online (Sandbox Code Playgroud)

以下结构用于存储每个元素:

typedef struct {
    int typ;
    union {
        char *str;
        int   val;
    }
} tElem;
Run Code Online (Sandbox Code Playgroud)

那么每个元素的大小就是最大联合的大小(类型为4个字节,联合为4个字节,尽管这些是典型值,实际大小依赖于实现).

要创建"set"元素,您可以使用:

tElem e;
e.typ = ELEM_SYM_SET;
Run Code Online (Sandbox Code Playgroud)

要创建"变量[b]"元素,您可以使用:

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

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

tElem e;
e.typ = ELEM_CONSTANT;
e.val = 7;
Run Code Online (Sandbox Code Playgroud)

你可以轻松扩展它以包括浮点数(float flt)或有理数(struct ratnl {int num; int denom;})和其他类型.

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

       +-----------+
0x1010 |           |
0x1011 |    typ    |
0x1012 |           |
0x1013 |           |
       +-----+-----+
0x1014 |     |     |
0x1015 | str | val |
0x1016 |     |     |
0x1017 |     |     |
       +-----+-----+
Run Code Online (Sandbox Code Playgroud)

如果它只是在一个结构中,它看起来像这样:

       +-------+
0x1010 |       |
0x1011 |  typ  |
0x1012 |       |
0x1013 |       |
       +-------+
0x1014 |       |
0x1015 |  str  |
0x1016 |       |
0x1017 |       |
       +-------+
0x1018 |       |
0x1019 |  val  |
0x101A |       |
0x101B |       |
       +-------+
Run Code Online (Sandbox Code Playgroud)


Mar*_*rio 7

我想说它可以更容易地重用可能以不同方式使用的内存,即节省内存.例如,你想做一些能够保存短字符串和数字的"变体"结构:

struct variant {
    int type;
    double number;
    char *string;
};
Run Code Online (Sandbox Code Playgroud)

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

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

struct variant {
    int type;
    union {
        double number;
        char *string;
    } value;
};
Run Code Online (Sandbox Code Playgroud)

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


Xia*_*ofu 5

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

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

例如:

typedef union MyUnion MYUNION;
union MyUnion
{
   int MyInt;
   float MyFloat;
};
Run Code Online (Sandbox Code Playgroud)

MyUnion将只包含一个int OR浮点数,具体取决于您最近设置的值.这样做:

MYUNION u;
u.MyInt = 10;
Run Code Online (Sandbox Code Playgroud)

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

u.MyFloat = 1.0;
Run Code Online (Sandbox Code Playgroud)

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

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

  • 为了记录,分配给浮点数然后打印整数将*不会*导致错误,因为编译器和运行时环境都*不知道*哪个值是有效的。当然,打印出来的 int 对于大多数用途来说毫无意义。它只是浮点数的内存表示,解释为 int。 (2认同)

Ada*_*wis 5

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

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..
    }
}
Run Code Online (Sandbox Code Playgroud)

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