C和C ++中的联合的目的

我早些时候很舒服地使用过工会; 今天,当我阅读这篇文章并且知道这段代码时,我感到非常惊慌

union ARGB
{
    uint32_t colour;

    struct componentsTag
    {
        uint8_t b;
        uint8_t g;
        uint8_t r;
        uint8_t a;
    } components;

} pixel;

pixel.colour = 0xff040201;  // ARGB::colour is the active member from now on

// somewhere down the line, without any edit to pixel

if(pixel.components.a)      // accessing the non-active member ARGB::components

实际上是未定义的行为,即从最近写入的联盟成员读取会导致未定义的行为。 如果这不是工会的预期用法,那是什么? 有人可以详细解释吗?

更新:

我想事后澄清几件事。

  • 对于C和C ++来说,这个问题的答案并不相同; 我的无知年轻的自我标记为C和C ++。
  • 通过C ++ 11标准的搜索后,我无法确定地说它调用访问/检查非活动联合成员是未定义的/未指定的/实现定义的。 我能找到的所有内容都是§9.5/ 1:

    如果标准布局联合包含多个共享初始序列的标准布局结构,并且此标准布局联合类型的对象包含其中一个标准布局结构,则允许检查任何公共初始序列的标准布局结构成员。 §9.2/ 19:如果相应的成员具有布局兼容类型,并且两个标准布局结构都共享一个共同的初始序列,并且这两个成员都不是位域,或者都是一个或多个初始序列的具有相同宽度的位域成员。

  • 在C中(C99 TC3 - DR 283起),这样做是合法的(感谢Pascal Cuoq提出这一点)。 但是,如果读取的值发生无效(所谓的“陷阱表示”),则试图执行此操作仍然会导致未定义的行为。 否则,读取的值是实现定义的。
  • C89 / 90在未指明行为的情况下(附件J)对此进行了说明,K&R的书中说明了它的实施定义。 来自K&R的报价:

    这是一个联合的目的 - 一个单一的变量,可以合法地持有几种类型的任何一种。 只要使用一致:检索的类型必须是最近存储的类型。 程序员有责任跟踪当前存储在工会中的类型; 如果某个东西被存储为一个类型并被提取为另一个类型,则结果与实现相关。

  • 从Stroustrup的TC ++ PL中提取(重点是我的)

    对于“类型转换”有时被滥用的数据的兼容性而言,使用联合可能是必不可少的。

  • 最重要的是,这个问题(自从我的问题以来,其标题保持不变)的目的是理解工会的目的,而不是标准允许的内容。例如,当然,C ++标准允许使用继承进行代码重用,但将继承作为C ++语言功能引入的目的或初衷并非如此。 这就是安德烈的答案继续保持为接受的原因。


    工会的目的相当明显,但由于某种原因,人们常常错过它。

    联合的目的是通过使用相同的内存区域在不同的时间存储不同的对象来节省内存。 而已。

    它就像旅馆里的一个房间。 不同的人生活在非重叠的时间段内。 这些人永远不会见面,而且一般都不了解对方。 通过恰当地管理房间分时(即确保不同的人不会同时分配到一个房间),相对较小的酒店可以为相对大量的人提供住宿,这是什么酒店是给。

    这正是联盟所做的。 如果您知道程序中的多个对象具有不重复的值生命周期值,则可以将这些对象“合并”为一个联合,从而节省内存。 就像酒店房间在每个时刻最多只有一个“活跃”租户一样,工会在每个节目时间最多只有一个“活跃”会员。 只有“活动”成员可以阅读。 通过写入其他成员,您可以将“活动”状态切换到该其他成员。

    出于某种原因,工会的这个最初目的被完全不同的东西“覆盖”:写一个工会的一个成员,然后通过另一个成员检查它。 这种内存重新解释(又名“类型双关”)不是工会的有效使用。 它通常导致未定义的行为被描述为在C89 / 90中产生实现定义的行为。

    编辑:对于C99标准的技术勘误之一(参见DR#257和DR#283),使用工会用于类型剔除(即写入一个成员然后再读取另一个成员)的目的得到了更详细的定义。 但是,请记住,正式情况下,这不会通过尝试读取陷阱表示来防止发生未定义的行为。


    您可以使用联合来创建如下的结构,其中包含一个字段,用于告诉我们联合的哪个组件实际使用:

    struct VAROBJECT
    {
        enum o_t { Int, Double, String } objectType;
    
        union
        {
            int intValue;
            double dblValue;
            char *strValue;
        } value;
    } object;
    

    从语言的角度来看,这种行为是不确定的。 考虑到不同平台在内存对齐和字节顺序上可能有不同的限制。 大端序与小序端机器中的代码将不同地更新结构中的值。 修复语言中的行为将要求所有实现都使用相同的字节顺序(和内存对齐限制...)来限制使用。

    如果您使用的是C ++(您使用两个标记),并且您真的关心可移植性,那么您可以使用该结构并提供一个setter,它接受uint32_t并通过位掩码操作适当地设置字段。 使用函数C也可以做到这一点。

    编辑 :我期待AProgrammer写下投票的答案并关闭这一个。 正如一些评论指出的那样,通过让每个实现决定做什么,排列和填充也可以以不同的方式处理,从而在标准的其他部分处理了排序。 现在,AProgrammer暗示的严格别名规则在这里很重要。 允许编译器对变量的修改(或缺少修改)作出假设。 在联合的情况下,编译器可以重新排序指令并将每个颜色分量的读取移到写入颜色变量上。

    链接地址: http://www.djcxy.com/p/73543.html

    上一篇: Purpose of Unions in C and C++

    下一篇: Is !! a safe way to convert to bool in C++?