使用std :: launder获取指向活动联合成员的指针,而不是指向非活动联合成员的指针?

时间:2019-05-25 19:37:23

标签: c++ pointers language-lawyer c++17 undefined-behavior

考虑这个联合:

union A{
  int a;
  struct{
    int b;
    } c;
  };

ca不是layout-compatibles类型,因此无法通过b来读取a的值:

A x;
x.c.b=10;
x.a+x.a; //undefined behaviour (UB)

有关审判1和审判2的信息,请参见this question

审判3

现在让我们使用std::launder来实现预期的目的:

A x;
x.a=10;
auto p = &x.a;                 //(1)
x.c.b=12;                      //(2)
p = std::launder(p);           //(2')
*p+*p;                         //(3)  UB?

std::launder可以更改任何内容吗?根据{{​​3}}:

  

template <class T> constexpr T* launder(T* p) noexcept;

     

要求p代表内存中字节的地址 A 。在其生存期内且类型类似于T的对象 X 位于地址 A 中。通过结果可以访问的所有存储字节都可以通过p进行访问(请参见下文)。

     

返回:类型为T *的值,它指向 X

     

备注:只要在核心常量表达式中使用其参数的值,就可以在核心常量表达式中使用此函数的调用。 通过指向对象Y的指针值可达到一个存储字节,如果它位于Y占用的存储空间,可与Y指针互换的对象或立即封闭的数组对象中如果Y是数组元素。如果T是函数类型或cv void,则程序格式错误。

黑体字强调了困扰我的东西。如果p是无效的指针值,那么如何访问任何存储字节?另一方面,std::launder就是不可用。

否则,可以将p的值作为(2)的指针值,该值代表[ptr.launder]的“注释”中提到的存储区域

  

如果不满足这些条件,则可以通过调用std​::​launder([support.dynamic])从表示其存储地址的指针中获取指向该新对象的指针。

1 个答案:

答案 0 :(得分:8)

注释中允许明确

basic.life contains the following rule使std::launder变得不必要:

  

如果在对象的生存期结束之后且在重新使用或释放​​该对象所占用的存储之前,在原始对象所占用的存储位置上创建了一个指向原始对象的指针的新对象,引用原始对象的引用或原始对象的名称将自动引用新对象,并且一旦开始新对象的生存期,就可以使用它来操作新对象,如果:

     
      
  • 新对象的存储空间正好覆盖了原始对象所占据的存储位置,并且
  •   
  • 新对象的类型与原始对象相同(忽略顶级cv限定词),并且
  •   
  • 原始对象的类型不是const限定的,并且,如果是类类型,则不包含任何类型为const限定的非静态数据成员或引用类型,并且
  •   
  • 原始对象和新对象都不是潜在重叠的子对象。
  •   
     

[注:如果不满足这些条件,则可以通过调用std::launder从表示其存储地址的指针中获取指向新对象的指针。 —尾注]

这种“新对象是在原始对象所占的存储位置创建的”案例在此明确适用,因为:

  

An object is created ... when implicitly changing the active member of a union...

所有项目符号条件都得到满足,因为"potentially overlapping subobject"指的是基类子对象,而联合成员不是。 (在您链接的版本中,该项目符号直接提到了基类子对象。)

然而,即使这种解释将针对工会而改变,但该注释也特别提到std::launder绕过了这一限制。

请注意,较早版本的Standard将该子对象排除在此规则之外...但是注释清楚表明,std::launder也将绕过该问题。