C:联合在实际中的使用场景是什么?

32

我手头有一个例子,其中类型的对齐是通过 union max_align 来保证的。我正在寻找一个更简单的例子,其中实际使用 union,以便向我的朋友解释。


你是指C/C++中的union结构吗? - Traveling Tech Guy
5
通常我看到人们使用 union 来实现一种简单的多态形式。 - E.M.
为什么不使用Google代码搜索?它会带你找到大量实用的例子 :) http://www.google.com/codesearch?hl=de&sa=N&q=union++lang:c&ct=rr&cs_r=lang:c - Christian
@Christian 链接无效。 - Koray Tugay
18个回答

33

当我解析文本时,通常使用联合体。我使用类似于以下的代码:

typedef enum DataType { INTEGER, FLOAT_POINT, STRING } DataType ;

typedef union DataValue
{
    int v_int;
    float v_float;
    char* v_string;
}DataValue;

typedef struct DataNode
{
    DataType type;
    DataValue value;
}DataNode;

void myfunct()
{
    long long temp;
    DataNode inputData;

    inputData.type= read_some_input(&temp);

    switch(inputData.type)
    {
        case INTEGER: inputData.value.v_int = (int)temp; break;
        case FLOAT_POINT: inputData.value.v_float = (float)temp; break;
        case STRING: inputData.value.v_string = (char*)temp; break;
    }
}

void printDataNode(DataNode* ptr)
{
   printf("I am a ");
   switch(ptr->type){
       case INTEGER: printf("Integer with value %d", ptr->value.v_int); break;
       case FLOAT_POINT: printf("Float with value %f", ptr->value.v_float); break;
       case STRING: printf("String with value %s", ptr->value.v_string); break;
   }
}

如果你想看如何大量使用union,可以查看任何使用flex/bison的代码。例如,可以看看splint,它包含了大量的union。


带有模式匹配的标签联合在C中。许多人使用没有特定类型标记的联合体吗? - Roman A. Taycher
在声明中,你不应该为DataNode添加别名吗? - Koray Tugay

6

在需要查看数据的不同方面时,我通常使用联合(unions)。 例如,当你想要查看32位颜色值时,你可以同时查看32位值以及红、绿、蓝和透明度组件。

struct rgba
{
  unsigned char r;
  unsigned char g;
  unsigned char b;
  unsigned char a;
};

union  
{
  unsigned int val;
  struct rgba components;
}colorval32;

NB 你也可以通过位掩码和移位来实现相同的功能,即:
#define GETR(val) ((val&0xFF000000) >> 24)

但我认为联合方法更加优雅


2
但是 struct rgba 可能具有填充,即使没有填充,sizeof(unsigned int) 也可能不等于 sizeof(struct rgba)(没有填充为4)。也就是说,你可能不想这样做。 - Alok Singhal
3
@Alok:更不用说struct中的rgba字段会按顺序出现(有或没有填充),而unsigned int字节顺序将取决于大小端。换句话说,这段代码不可移植,在另一个处理器上编译时可能会以奇怪的方式失败。 - David Thornley
7
请注意,(标准)C语言不支持这种操作——将值分配给联合体的一个成员,然后从另一个成员读取该值的行为是未定义的。然而,编译器/系统实现通常以它们自己特定的方式来支持这种操作。 - nos
1
感谢大家的评论。正如你们所说,这并不是可移植的(也不打算这样做),并且依赖于许多我在帖子中没有明确说明的假设。通常我不会使用unsigned int,而是像typedef uint32_t这样的东西,即我知道它是32位的。我将修改我的帖子以使其更清晰。 - zebrabox
除了可移植性问题之外,这也是混淆编译器和可能的别名的好方法,迫使它跳过一堆本来有用的优化。 - jalf
关于nos所说的内容,我认为他的意思是由于严格别名规则,这是未定义行为。当您将char用作“其他”联合类型时,应该可以正常工作。 - v.oddou

5

如果要通过将特定端口映射到内存来按字节和按位访问寄存器或I/O端口,请参见以下示例:

    typedef Union
{
  unsigned int a;
struct {
  unsigned bit0 : 1,
           bit1 : 1,
           bit2 : 1,
           bit3 : 1,
           bit4 : 1,
           bit5 : 1,
           bit6 : 1,
           bit7 : 1,
           bit8 : 1,
           bit9 : 1,
           bit10 : 1,
           bit11 : 1,
           bit12 : 1,
           bit13 : 1,
           bit14 : 1,
           bit15 : 1
} bits;
} IOREG;

# define PORTA (*(IOREG *) 0x3B)
...
unsigned int i = PORTA.a;//read bytewise
int j = PORTA.bits.bit0;//read bitwise
...
PORTA.bits.bit0 = 1;//write operation

6
处理大/小端转换时不要使用这个。 - mouviciel
1
@mouviciel:这不受字节序影响... 如果可以的话,你能否支持一下你的评论? - wrapperm
8
根据 C 标准:“位域在单元内的分配顺序(从高到低或从低到高)由实现定义。可寻址存储单元的对齐方式未指定。” - Alok Singhal
1
然而,在许多情况下,寄存器或I/O端口确实是特定于平台的,因此平台无关的代码并不是一个问题。虽然我想你可能会为一些在大端和小端机器上都使用的硬件编写驱动程序(例如,用于图形芯片的Linux驱动程序)...在那种情况下,我相信你知道自己在做什么。 - Craig McQueen
1
可通过简单的程序轻松确定可寻址存储单元的对齐方式...实际上,在为特定平台编码之前,您应该了解架构字节序等相关知识... - wrapperm
1
这里缺少一条免责声明,即“从非活动联合成员读取会产生未定义的行为”。 - Sebastian Mach

3
在Windows世界中,unions通常用于实现标记变量,这是(或在.NET之前?)在COM对象之间传递数据的一种标准方式。
其思想是,union类型可以提供一个单一的自然接口,用于在两个对象之间传递任意数据。某些COM对象可以向您传递一个变量(例如类型为VARIANT_variant_t的变量),其中可能包含doublefloatint或其他类型的数据。
如果您需要处理Windows C++代码中的COM对象,则会在各个地方看到变量类型。

2
struct cat_info
{
int legs;
int tailLen;
};

struct fish_info
{
bool hasSpikes;
};


union 
{
fish_info fish;
cat_info cat;
} animal_data;

struct animal
{
char* name;
int animal_type;
animal_data data;
};

我知道如何使用联合,我的朋友也知道,但我想知道它最常用的地方。 - yesraaj

2

如果你有不同类型的消息,使用联合体很有用,这样你就不需要在任何中间级别了解确切类型。只有发送者和接收者需要解析实际消息。任何其他级别只需要知道尺寸和可能的发送者和/或接收者信息。


这不就是多态的用途吗?一个通用消息内容的基类,然后每个消息都是基类的特化。 - Thomas Matthews
1
@Thomas:C 语言中没有类。 - Adriano Varoli Piazza

2

1

你是指像这样的吗?

union {
   long long a;
   unsigned char b[sizeof(long long)];
} long_long_to_single_bytes;

新增内容:

最近我在我们的AIX机器上使用了这个方法,将64位机器标识符转换为字节数组。

std::string getHardwareUUID(void) {
#ifdef AIX
   struct xutsname m; // aix specific struct to hold the 64bit machine id
   unamex(&b);        // aix specific call to get the 64bit machine id
   long_long_to_single_bytes.a = m.longnid;
   return convertToHexString(long_long_to_single_bytes.b, sizeof(long long));
#else // Windows or Linux or Solaris or ...
   ... get a 6byte ethernet MAC address somehow and put it into mac_buf
   return convertToHexString(mac_buf, 6);
#endif

是的,但我想知道它在实际应用中的情况,例如max_align。 - yesraaj
如果您使用类似于此的内容,则应注意字节顺序(字节序)。这意味着此代码可能在一台机器上正常工作(大端),但在另一台机器上可能无法正常工作(小端)。 - Yousf
它可以在大端和小端机器上运行。只是结果不同 :-) - anon
@yesraaj:我最近在我们的AIX机器上使用了这个工具,将64位机器标识符转换为字节数组。请注意:这个机器标识符是AIX特有的。 - anon

1

我有时会这样使用联合体

//Define type of structure
typedef enum { ANALOG, BOOLEAN, UNKNOWN } typeValue_t;
//Define the union
typedef struct  {
  typeValue_t typeValue;
  /*On this structure you will access the correct type of
    data according to its type*/
  union {
    float ParamAnalog;
    char  ParamBool;
  };
} Value_t;

然后你可以声明不同类型的数组,更高效地存储数据,并进行一些“多态”操作,例如:

 void printValue ( Value_t value ) {
    switch (value.typeValue) {
       case BOOL:
          printf("Bolean: %c\n", value.ParamBool?'T':'F');
          break;
       case ANALOG:
          printf("Analog: %f\n", value.ParamAnalog);
          break;
       case UNKNOWN:
          printf("Error, value UNKNOWN\n");
          break;
    }
 }

1

这里是另一个使用联合体可能有用的例子。

(不是我的想法,我在一份讨论c++优化的文档中发现了这个想法)

引用开始

...联合体也可以用于节省空间,例如:

首先是非联合方法:

void F3(bool useInt) {
    if (y) {
        int a[1000];
        F1(a);  // call a function which expects an array of int as parameter
    }
    else {
        float b[1000];
        F2(b);  // call a function which expects an array of float as parameter
    }
}

在这里,可以使用相同的内存区域来存储a和b,因为它们的生命周期不重叠。通过将a和b合并到一个联合体中,您可以节省大量cpu-cache空间

void F3(bool useInt) {

    union {
        int a[1000];
        float b[1000];
    };

    if (y) {
        F1(a);  // call a function which expects an array of int as parameter
    }
    else {
        F2(b);  // call a function which expects an array of float as parameter
    }
}

使用联合是一个不安全的编程实践,因为如果a和b的使用重叠,编译器会没有任何警告。你只应该在需要大量缓存空间的大型对象上使用这种方法。

结束引用


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接