代码之家  ›  专栏  ›  技术社区  ›  ysap

ANSIC联合-它们真的有用吗?

  •  6
  • ysap  · 技术社区  · 14 年前

    从昨天对某个问题的回答中,我了解到,写入一个联合成员并从另一个不同类型的成员读取值是不重要的和不安全的,假定这些成员是基本对齐的。因此,经过一些研究,我找到了一个重复这个声明并指定了一个流行的例子的书面来源——使用int和float的并集来查找float的二进制表示。

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

    注:也就是说,在C标准下,很明显,为了具体实施,这些规则是预先知道的,可以利用。

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

    编辑2:因为这一点在答案中重复- 节省内存 是有效参数。我想知道是否还有别的事情。

    10 回复  |  直到 6 年前
        1
  •  10
  •   strager    14 年前

    对。

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

    那里 但是,其中一个特性只能在需要时使用,而且很少需要。

        2
  •  4
  •   jamesdlin    14 年前

    即使 union S不提供太多即时的有用性(除了减少内存使用),使用 联盟 将其所有成员 struct 它使预期的语义清晰:只有一个值(如果它是 联盟 属于 结构 s)在任何给定时间有效。它能更好地记录自己。

    如果你把所有的 联盟 成员独立的成员 结构 . 此外,如果您读取一个以前没有写入的成员,但现在您还需要考虑应用程序的语义(它是否将所有未使用的成员初始化为0),那么仍然存在定义不正确的行为问题。是不是把它们当成垃圾了?所以在这个意义上,为什么 不会 你用工会?

        3
  •  3
  •   jacknad    14 年前

    是的,联合可以是不可移植的和不安全的,但有其用途。例如,它可以通过消除将uint32强制转换为char[4]来加快速度。如果您试图在sw中按IP地址路由,这可能会很方便,但是您的处理器endian必须是网络顺序的。把联管节看作是铸造的一种替代方法,使用更少的机器指令。铸造也有类似的缺点。

        4
  •  3
  •   detly    14 年前

    该问题包含一个约束,该约束可能不允许有效的答案…

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

    换句话说:工会不必 适用于标准定义的行为 一般来说,为了有用,它们可以简单地允许某人利用目标机器的特性,而无需使用汇编。

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

    我希望这是有道理的。

        5
  •  3
  •   paxdiablo    14 年前

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

    它们允许您将多个值中的一个值存储到单个内存块中,并沿以下行存储:

    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.
    

    如果你用的话就可以了 类型 决定 type1 变量存储在那里。

        6
  •  3
  •   R.. GitHub STOP HELPING ICE    14 年前

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

    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字节,而如果没有使用联合,则为40-80字节。如果我想分别保存每个可能的原始数字类型(有符号char、short、int、long、long long、unsigned char、unsigned short等),而不是在存储它们之前将它们转换成最大的有符号/无符号/浮点类型,那么差异会更大。

    此外,虽然它不是“可移植的”,但它假定除了 unsigned char ,标准允许使用 无符号字符 或将指针投射到 unsigned char * 以这种方式访问任意数据对象。如果您将这些信息写入磁盘,它将无法移植到使用不同表示形式的其他系统,但在运行时它可能仍然有用-例如,实现要存储的哈希表 double 价值观。(如果填充位问题导致此技术无效,有人想更正我吗?)如果没有其他内容,则可以使用它来实现 memcpy (不是很有用,因为标准库为您提供了更好的实现)或者(更有趣的是)a memswap 函数,它可以用有界临时空间交换两个任意大小的对象。这已经有点超出了工会的使用范围。 无符号字符 铸造领土,但它是密切相关的。

        7
  •  2
  •   BuggerMe    14 年前

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

    假设您有一个结构作为缓冲区

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

    编辑:这里有一个例子

    struct X
    {
      int a;
    };
    
    struct Y
    {
      int b;
    };
    
    union Public
    {
       struct X x;
       struct Y y;
    };
    

    在这里,使用union x y的人可以将x y强制转换为结构x或y

    所以给定一个函数:

    void foo(Public* arg)
    {   
    ...
    

    您可以访问结构X或结构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;
    ...
    
        8
  •  2
  •   Jerry Coffin    14 年前

    使用union进行类型压缩是不可移植的(尽管比任何其他类型压缩方法都不可移植)。

    例如,解析器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
    

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

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

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

    当然,使用联合来保存内存已经不再重要了。虽然现在它看起来很普通,但在64kb的RAM非常多的时候,内存节省意味着更多。

        9
  •  0
  •   Patrick Lafferty    13 年前

    考虑一个具有不同位字段的硬件控制寄存器。通过在寄存器的这些位字段中设置值,我们可以控制寄存器的不同功能。

    通过使用union数据类型,我们可以修改寄存器的整个内容或寄存器的特定位字段。

    对于EX: 考虑如下联合数据类型:

    /* 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;
    

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

        10
  •  0
  •   Jose    6 年前

    下面是一个实际例子:

    有一些微控制器,它们的非易失性存储器将数据存储在字节块中。 你怎么能轻易地在记忆中存储一组浮点数呢? 我们知道在C中,浮点数的长度是32位(4字节),因此:

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

    现在,您可以使用float_int8类型的变量/指针存储/地址浮动,并且使用循环,您可以很容易地将它们存储在内存中,作为已分解的字节,而无需进行任何转换或分解。同样的故事在读记忆的时候重复着。即使您不需要知道如何将浮点数分解成字节来存储或恢复存储在内存中的数据。

    这个例子摘自我自己的工作。所以是的,它们是有用的。