将联合重新解释为不同的联合

时间:2018-02-14 14:55:42

标签: c++ c++11 language-lawyer

我有一个标准布局联合,其中包含大量类型:

union Big {
    Hdr h;

    A a;
    B b;
    C c;
    D d;
    E e;
    F f;
};

每个类型AF都是标准布局,并且第一个成员是Hdr类型的对象。 Hdr标识了union的活动成员是什么,所以这是变体的。现在,我确实知道(因为我检查过)活跃成员是B还是C。实际上,我已将空间缩小到:

union Little {
    Hdr h;

    B b;
    C c;
};

现在,以下定义明确或未定义的行为是什么?

void given_big(Big const& big) {
    switch(big.h.type) {
    case B::type: // fallthrough
    case C::type:
        given_b_or_c(reinterpret_cast<Little const&>(big));
        break;
    // ... other cases here ...
    }
}

void given_b_or_c(Little const& little) {
    if (little.h.type == B::type) {
        use_a_b(little.b);
    } else {
        use_a_c(little.c);
    }
}

Little的目标是有效地充当文档,我已经检查过它是BC所以将来没有人添加代码检查它是A还是其他什么。

我是否正在将B子对象作为B阅读,以使其格式良好?可以在这里有意义地使用公共初始序列规则吗?

4 个答案:

答案 0 :(得分:18)

为了能够获取指向A的指针,并将其重新解释为指向B的指针,它们必须是指针可互换的

Pointer-interconvertible是关于对象,而不是对象类型

在C ++中,地方有对象。如果您在特定地点有一个Big并且至少有一个成员存在,那么由于指针可互换性,同一地点也会有一个Hdr

但是那个地方没有Little个对象。如果那里没有Little个对象,则它不能与指针可互换,而Little对象不存在。

它们似乎是布局兼容的,假设它们是平面数据(普通旧数据,可以轻易复制等)。

这意味着你可以复制他们的字节表示并且它可以工作。事实上,优化器似乎理解堆栈本地缓冲区的memcpy,一个新的放置(使用普通的构造函数),然后memcpy实际上是一个noop。

template<class T>
T* laundry_pod( void* data ) {
  static_assert( std::is_pod<Data>{}, "POD only" ); // could be relaxed a bit
  char buff[sizeof(T)];
  std::memcpy( buff, data, sizeof(T) );
  T* r = ::new( data ) T;
  std::memcpy( data, buff, sizeof(T) );
  return r;
}

上述函数在运行时是一个noop(在优化版本中),但它将data的T-layout兼容数据转换为实际T

因此,如果我是对的,BigLittleBigLittle中的类型的子类型时是布局兼容的,您可以这样做:< / p>

Little* inplace_to_little( Big* big ) {
  return laundry_pod<Little>(big);
}
Big* inplace_to_big( Little* big ) {
  return laundry_pod<Big>(big);
}

void given_big(Big& big) { // cannot be const
  switch(big.h.type) {
  case B::type: // fallthrough
  case C::type:
    auto* little = inplace_to_little(&big); // replace Big object with Little inplace
    given_b_or_c(*little); 
    inplace_to_big(little); // revive Big object.  Old references are valid, barring const data or inheritance
    break;
  // ... other cases here ...
  }
}

如果Big包含非平面数据(如引用或const数据),则上述情况可能非常糟糕。

请注意laundry_pod不进行任何内存分配;它使用展示位置new,使用T处的字节在data指向的位置构建data。虽然看起来它正在做很多事情(复制内存),但它会优化为noop。

有一个“对象存在”的概念。对象的存在几乎与在物理或抽象机器中写入的位或字节无关。您的二进制文件上没有与“现在存在对象”相对应的指令。

但语言有这个概念。

不存在的对象无法与之交互。如果这样做,C ++标准不会定义程序的行为。

这允许优化器假设您的代码执行什么,不执行什么操作以及无法访问哪些分支以及哪些分支可以到达。它允许编译器进行无混叠假设;通过指针或对A 的引用修改数据不能更改通过指针或B引用到达的数据,除非A和B都存在于同一位置。

编译器可以证明BigLittle对象不能同时存在于同一位置。因此,不能通过指针或对Little的引用来修改任何数据,而是可以修改Big类型的变量中存在的任何内容。反之亦然。

想象一下,如果given_b_or_c修改字段。那么编译器可以内联given_biggiven_b_or_c以及use_a_b,注意没有Big的实例被修改(只是Little的一个实例),并证明

这为它保存了一条加载指令,优化器非常高兴。但现在您的代码如下:

Big

被优化为

Big b = whatever;
b.foo = 7;
((Little&)b).foo = 4;
if (b.foo!=4) exit(-1);

因为它可以证明Big b = whatever; b.foo = 7; ((Little&)b).foo = 4; exit(-1); 必须是b.foo它已设置一次且从未修改过。由于别名规则,7访问无法修改Little

现在这样做:

Big

并假设大的那里没有变化,因为有一个memcpy和Big b = whatever; b.foo = 7; (*laundry_pod<Little>(&b)).foo = 4; Big& b2 = *laundry_pod<Big>(&b); if (b2.foo!=4) exit(-1); 可以合法地改变数据的状态。没有严格的别名冲突。

它仍然可以跟随::new并消除它。

memcpy的{​​{3}}被优化了。请注意,如果没有优化,代码必须具有条件和printf。但因为它是,它被优化为空程序。

答案 1 :(得分:4)

我在n4296(草案C ++ 14标准)中找不到任何可以使其合法化的措辞。更重要的是,我不能甚至找到任何给出的措辞:

union Big2 {
    Hdr h;

    A a;
    B b;
    C c;
    D d;
    E e;
    F f;
};

我们可以reinterpret_cast引用Big引用Big2,然后使用引用。 (请注意,BigBig2 布局兼容。)

答案 2 :(得分:2)

这是UB遗漏。 [expr.ref] /4.2:

  

如果E2是非静态数据成员且E1的类型是“cq1 vq1 X”,   并且E2的类型是“cq2 vq2 T”,表达式[E1.E2]表示   第一个表达式指定的对象的命名成员。

在评估given_b_or_c中的given_big来电期间,little.h中的对象表达式实际上并未指定Little个对象,并且那里没有这样的成员。因为标准&#34;省略了任何明确的行为定义&#34;对于这种情况,行为是未定义的。

答案 3 :(得分:-1)

我不确定,这是否真的适用于此。在reinterpret_cast - Notes部分,他们讨论了指针可互换的对象。

来自[basic.compound]/4

  

如果出现以下情况, a b 两个对象指针可互换

     
      
  • 它们是同一个对象,或
  •   
  • 一个是union对象,另一个是该对象的非静态数据成员,或
  •   
  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员,或者,如果该对象没有非静态数据成员,则该对象的第一个基类子对象,或者< / LI>   
  • 存在一个对象 c ,使 a c 是指针可互换的, c 和< em> b 是指针可互换的。
  •   
     

如果两个对象是指针可互换的,那么它们具有相同的地址,并且可以通过reinterpret_­cast从指向另一个的指针获得一个指针。

在这种情况下,我们将Hdr h;(c)作为两个联合中的非静态数据成员,这应该允许(因为第二个和最后一个项目符号点)

Big* (a) -> Hdr* (c) -> Little* (b)