ANSI C联合 - 它们真的有用吗?

时间:2010-08-12 03:00:35

标签: c unions

从昨天对某个问题的回答中,我了解到写入一个联盟成员并从另一个不同类型的成员中读取值是不可移植和不安全的,假设成员的基础对齐。所以在经过一些研究之后,我发现了一个重复这个主张的书面来源并指定了一个流行的例子 - 使用int和float的联合来查找浮点数的二进制表示。

所以,我知道这个假设是不安全的,我想 - 除了节省记忆(呃......)对工会有什么实际用途?

注意:也就是说,在标准C下。显然,对于特定的实现,规则是事先已知的并且可以被利用。

编辑:“不安全”这个词,由于近年来的联系,可能是一个不好的措辞选择,但我认为意图明确。

编辑2:由于这一点在答案中重复 - 保存记忆是一个有效的参数。我想知道是否还有其他的东西。

10 个答案:

答案 0 :(得分:10)

提供创建通用容器的方法。但是,要获得多态行为,您必须自己实现vtable或类型切换......

,但是,这些功能之一只在您需要时才使用,而且很少需要。

答案 1 :(得分:4)

即使union没有立即提供有用的东西(减少内存使用量),使用union而不是将其所有成员转储到struct的一个好处是它使预期的语义清晰:只有一个值(或一组值,如果它是union的{​​{1}})在任何给定时间都有效。它更好地记录了自己。

如果您将所有struct成员分别成为union的成员,那么成员的互斥性就不那么明显了。此外,如果您读取以前未写入的成员,您仍然会遇到与定义不明确行为相同的问题,但现在您也需要考虑应用程序的语义(它是否将所有未使用的成员初始化为0?它会把它们留作垃圾吗?),所以从这个意义上说,为什么不会你使用联盟?

答案 2 :(得分:3)

是的,工会可能是不可移植的,不安全但有其用途。例如,它可以通过消除将uint32转换为char [4]的需要来加快速度。如果您尝试通过SW中的IP地址进行路由,这可能会派上用场,但您的处理器端序必须是网络顺序。将工会视为铸造的替代方案,减少机器指令。铸造也有类似的缺点。

答案 3 :(得分:3)

该问题包含可能禁止有效答案的约束......

您询问标准下的实际使用情况,但“实际使用”可能允许知识渊博的程序员以标准委员会不希望预期或枚举的方式利用实现定义的行为。我并不是说标准委员会有一个特定的行为,但他们明确地希望将这种能力留在那里以有用的方式被利用。

换句话说:Unions不必对标准定义的行为有用通常很有用,它们可以简单地允许某人利用其目标机器的怪癖而不诉诸于组装

有可能有一百万种有用的方法在实现定义的各种机器上使用它们,并且以严格的可移植方式使用它们是零有用的方法,但是这些百万次实现定义的用法足以使它们标准化存在。

我希望这是有道理的。

答案 4 :(得分:3)

即使对已知对齐和打包的特定实现进行折扣,联合仍然有用。

它们允许您将多个值中的一个存储到单个内存块中,如下所示:

typedef struct {
    int type;
    union {
        type1 one;
        type2 two;
    }
} unioned_type;

是的, 不可移植,希望能够将您的数据存储到one并从two读取。但是如果你只是使用type来指定底层变量是什么,你就可以轻松地获得它而不必进行强制转换。

换句话说:

unioned_type ut;
ut.type = 1;
ut.one = myOne;
// Don't use ut.two here unless you know the underlying details.

很好,假设您使用type来确定type1变量存储在那里。

答案 5 :(得分:3)

以下是对工会的一种合法的便携式使用:

struct arg {
    enum type t;
    union {
        intmax_t i;
        uintmax_t u;
        long double f;
        void *p;
        void (*fp)(void);
    } v;
};

t中的类型信息相结合,struct arg可以包含任何数字或指针值。整个结构的大小可能是16-32字节,而如果没有使用union,则为40-80字节。如果我想分别保留每个可能的原始数字类型(signed char,short,int,long,long long,unsigned char,unsigned short,...)而不是将它们转换为最大的签名,那么差异会更加极端/ unsigned /浮点类型在存储之前。

此外,虽然对于unsigned char以外的类型的表示不是“可移植”的,但标准允许使用带有unsigned char的联合或者将指针强加给unsigned char *。 {1}}并以这种方式访问​​任意数据对象。如果将该信息写入磁盘,则无法将其移植到使用不同表示形式的其他系统,但它在运行时仍可能有用 - 例如,实现哈希表以存储double值。 (如果填充位问题导致此技术无效,任何人都想纠正我吗?)如果没有别的,它可以用于实现memcpy(不是非常有用,因为标准库为您提供了更好的实现)或(更有趣的是)memswap函数,它可以交换两个任意大小的对象和有界临时空间。现在这已成为工会的一小部分外部使用领域,并进入unsigned char *投射领域,但它密切相关。

答案 6 :(得分:2)

使用我所遇到的联合的一种方法是进行数据隐藏。

假设您有一个缓冲区结构

然后通过允许在某些模块中的struct上联合,您可以以不同的方式访问缓冲区的内容,或者根本不访问该特定模块中声明的union。

编辑:这是一个例子

struct X
{
  int a;
};

struct Y
{
  int b;
};

union Public
{
   struct X x;
   struct Y y;
};

这里使用联合XY的人可以将XY转换为struct X或Y

所以给出了一个函数:

void foo(Public* arg)
{   
...

您可以访问struct X或struct Y

但是你想限制访问权限,以便用户不知道X

联合名称保持不变,但结构X部分不可用(通过标题)

void foo(Public* arg)
{
   // Public is still available but struct X is gone, 
   // user can only cast to struct Y

   struct Y* p = (struct Y*)arg;
...

答案 7 :(得分:2)

使用联合进行类型双关语是不可移植的(尽管不比其他任何类型双关语方法的便携性差。)

例如,OTOH是一个解析器,通常有一个联合来表示表达式中的值。 [编辑:我正在用一个我希望更容易理解的解析器示例替换解析器示例]:

让我们考虑一个Windows资源文件。您可以使用它来定义菜单,对话框,图标等资源。如下所示:

#define mn1 2

mn1 MENU
{
    MENUITEM "File", -1, MENUBREAK
}

ico1 "junk.ico"

dlg1 DIALOG 100, 0, 0, 100, 100 
BEGIN
    FONT 14, "Times New Roman"
    CAPTION "Test Dialog Box"
    ICON ico1, 700, 20, 20, 20, 20
    TEXT "This is a string", 100, 0, 0, 100, 10
    LTEXT "This is another string", 200, 0, 10, 100, 10
    RTEXT "Yet a third string", 300, 0, 20, 100, 10
    LISTBOX 400, 20, 20, 100, 100
    CHECKBOX "A combobox", 500, 100, 100, 200, 10
    COMBOBOX 600, 100, 210, 200, 100
    DEFPUSHBUTTON "OK", 75, 200, 200, 50, 15
END

解析MENU会给出一个菜单定义;解析DIALOG会给出对话框定义等等。在解析器中,我们将其表示为联合:

%union { 
        struct control_def {
                char window_text[256];
                int id;
                char *class;
                int x, y, width, height;
                int ctrl_style;
        } ctrl;

        struct menu_item_def { 
                char text[256];
                int identifier;
        } item;

        struct menu_def { 
                int identiifer;
                struct menu_item_def items[256];
        } mnu;

        struct font_def { 
                int size;
                char filename[256];
        } font;

        struct dialog_def { 
                char caption[256];
                int id;
                int x, y, width, height;
                int style;
                struct menu_def *mnu;
                struct control_def ctrls[256];
                struct font_def font;
        } dlg;

        int value;
        char text[256];
};

然后我们通过解析特定类型的表达式来指定将生成的类型。例如,文件中的字体定义成为联合的font成员:

%type <font> font

为了澄清,<font>部分引用了生成的联合成员,第二个“字体”引用了将产生该类型结果的解析器规则。以下是此特定案例的规则:

font: T_FONT T_NUMBER "," T_STRING { 
    $$.size = $2; 
    strcpy($$.filename,$4); 
};

是的,理论上我们可以在这里使用结构而不是联合 - 但除了浪费内存之外,它只是没有意义。文件中的字体定义定义字体。除了它实际定义的字体之外,让它产生包含菜单定义,图标定义,数字,字符串等的结构是没有意义的。 [编辑结束]

当然,使用工会来节省内存很少是非常重要的。虽然现在通常看起来相当微不足道,但当64 Kb的RAM很多时,内存节省意味着更多。

答案 8 :(得分:0)

考虑使用不同位字段的硬件控制寄存器。通过设置寄存器的这些位域中的值,我们可以控制寄存器的不同功能。

通过使用Union Data类型,我们可以修改寄存器的整个内容或寄存器的特定位域。

对于Ex:     考虑一个union数据类型,如下所示,

/* Data1 Bit Defintion */
typedef union 
{
    struct STRUCT_REG_DATA
    {
        unsigned int u32_BitField1  : 3;
        unsigned int u32_BitField2  : 2;
        unsigned int u32_BitField3  : 1;
        unsigned int u32_BitField4  : 2;                
    } st_RegData;

    unsigned int u32_RegData;

} UNION_REG_DATA;

修改注册表的整个内容,

UNION_REG_DATA  un_RegData;
un_RegData. u32_RegData = 0x77;

修改单个位字段内容(对于Ex Bitfield3)

un_RegData.st_RegData.u32_BitField3 = 1;

两者都反映在同一个记忆中。然后可以将该值写入硬件控制寄存器的值。

答案 9 :(得分:0)

这是一个实际的例子:

有些微控制器的非易失性存储器将数据存储在字节块中。 您如何轻松地在这些记忆中存储一组浮点数? 我们知道C语言中的float的长度为32位(4字节),所以:

union float_uint8
{
    uint8 i[KNFLOATS*4]; //or KNFLOATS*sizeof(float)
    float f[KNFLOATS];
};

现在,您可以使用float_uint8类型的变量/指针存储/寻址浮点数,并通过循环,可以轻松将它们作为分解后的字节存储在内存中,而无需进行任何转换或分解。当读取内存时,也会重复同样的故事。即使您甚至不需要知道如何将浮点数分解为字节来存储或恢复存储在内存中的数据。

此示例摘自我自己的作品。是的,它们很有用。