【问题标题】:Does reuse storage start lifetime of a new object? [duplicate]重用存储是否开始新对象的生命周期? [复制]
【发布时间】:2014-10-28 17:28:46
【问题描述】:
#include <cstdlib>
struct B {
    virtual void f();
    void mutate();
    virtual ~B();
};
struct D1 : B { void f(); };
struct D2 : B { void f(); };
void B::mutate() {
    new (this) D2; // reuses storage — ends the lifetime of *this
    f(); // undefined behavior - WHY????
    ... = this; // OK, this points to valid memory
}

我需要解释一下为什么f() 调用有UB? new (this) D2; 重用存储,但它也为D2 调用构造函数,并开始新对象的生命周期。在这种情况下,f() 等于 this -&gt; f()那就是我们只是调用D2的成员函数f()谁知道为什么是UB?

【问题讨论】:

  • Placement-new 应该用于大多数派生类,并用相同类型的对象替换它们。这是您拥有 UB 的另一个原因,因为您不仅要替换基类子对象,还要用不同类型的对象替换它。
  • @0x499602D2 18.6.1.3 标准定义了这种放置新的行为,但没有说明应该在大多数派生类上使用 ,并创建相同类型的对象。
  • 3.8 "如果在对象的生命周期结束后 […],在原始对象占用的存储位置创建一个新对象,[…] 原始对象的名称 [… ] 将自动引用新对象 […] 并可用于操作新对象 […] 如果:原始对象是类型 T 的最衍生对象 (1.8) 并且新对象是类型最衍生的对象T(也就是说,它们不是基类子对象)。”
  • @St.Antario:标准规定“程序可以通过重用对象占用的存储空间或通过显式调用对象的析构函数来结束任何对象的生命周期具有非平凡析构函数的类类型。”在重用其内存之前,对象是否还活着并不重要。后来肯定不是。
  • @St.Antario:调用析构函数足以结束对象的生命周期,但不是必需的。

标签: c++ class inheritance struct


【解决方案1】:

该标准显示了此示例 § 3.8 67 N3690:

struct C {
  int i;
  void f();
  const C& operator=( const C& );
};

const C& C::operator=( const C& other) {
  if ( this != &other ) {
    this->~C(); // lifetime of *this ends
    new (this) C(other); // new object of type C created
    f(); // well-defined
  }
  return *this;
}

C c1;
C c2;
c1 = c2; // well-defined
c1.f(); // well-defined; c1 refers to a new object of type C

请注意,此示例在就地构造新对象之前终止对象的生命周期(与您的代码相比,它不调用析构函数)。

但即使你这样做了,标准也说:

如果,在对象的生命周期结束之后并且在存储之前 被占用的对象被重用或释放,一个新的对象是 在原始对象占用的存储位置创建,a 指向原始对象的指针,引用的引用 到原始对象,或者原始对象的名称将 自动引用新对象,并且一旦生命周期 新对象已启动,可用于操作新对象,如果:

——新对象的存储正好覆盖存储位置 原始对象占据了哪个,并且 - 新对象属于 与原始对象相同的类型(忽略顶层 cv 限定符)和

——原始对象的类型不是 const 限定,并且,如果是类类型,则不包含任何非静态 类型为 const 限定或引用类型的数据成员,以及

——原始对象是 T 类型的最衍生对象 (1.8),而 新对象是类型 T 派生度最高的对象(也就是说,它们不是 基类子对象)。

注意'and'字,以上条件都必须满足。

由于您没有满足所有条件(您将派生对象放置在基类对象的内存空间中),当使用隐式或显式使用此指针。

根据编译器的实现,这可能会或现在会崩溃,因为基类虚拟对象为 vtable 保留了一些空间,就地构造了一个派生类型的对象,该对象覆盖了一些虚函数意味着 vtable 可能不同,放置对齐问题和其他低级内部结构,您将拥有简单的 sizeof 不足以确定您的代码是否正确。

【讨论】:

  • 你是说他不是在终止旧对象的生命周期吗?标准说他是。
  • 他没有破坏对象,这可能非常糟糕。为了正确起见,我会指定这个,谢谢
【解决方案2】:

这个结构很有趣:

  • placement-new 不能保证调用对象的析构函数。因此,此代码将无法正确确保对象的生命周期结束。

  • 所以原则上你应该在重用对象之前调用析构函数。但随后您将继续执行已死对象的成员函数。根据标准 section.9.3.1/2 如果为非 X 类型或从 X 派生的类型的对象调用类 X 的非静态成员函数,则行为未定义。

  • 1234563

当你的新对象创建完成时,你当前对象的身份实际上在执行函数时已经改变了。您无法确定指向将被调用的虚函数的指针是在您的放置新(因此是指向 D1::f 的旧指针)之前还是之后(因此是 D2::f)读取的。

顺便说一句,正是由于这个原因,在联合中你能做什么或不能做什么有一些限制,在联合中,不同的活动对象共享相同的内存位置(参见第 9.5/2 点,特别是标准中的第 9.5/4 点)。

【讨论】:

  • 新放置不会清理旧对象,但会杀死它。如果对象的内存被重用,则对象的生命周期结束。
  • 是的,没错:一旦被覆盖,旧对象将不再显示生命迹象!然而“在对象的生命周期结束之后,在对象占用的存储空间被重用之前”强烈建议原则上生命周期的结束应该发生在重用之前,并且第 9.5/4 点证明这在另一种情况下。这就是为什么我制定了“不会正确确保”。例如,如果对象使用智能指针,这些将被直接覆盖,并且它们的引用计数将是错误的:使用对象可能会被杀死,但它仍然可能困扰代码!
  • 在对象的生命周期结束之后,在对象占用的存储空间被重用之前”是一种奇特的说法,在析构函数运行时, 如果您选择运行它。 “在对象的生命周期结束之后,在对象占用的存储被重用或释放之前,在原对象占用的存储位置创建一个新的对象”这行是点头事实上,记忆并没有物理消失。你可以用free()“释放”它,但你的libc仍然拥有它,并会通过malloc()把它还给你。
  • 我认为该规则也使得将类型 A 放入内存位置,创建一个指向它的指针,然后将类型 B 放在那里,然后将另一个类型 A 放在那里是非法的并使用原始指针。内存位置被 B 类型重用的事实不可撤销地破坏了原来的 A*
猜你喜欢
  • 2020-09-19
  • 2017-05-31
  • 1970-01-01
  • 1970-01-01
  • 2014-01-02
  • 1970-01-01
  • 1970-01-01
  • 2022-11-30
  • 1970-01-01
相关资源
最近更新 更多