是否允许在Base实例上编写Derived实例?

时间:2012-02-02 17:55:16

标签: c++ memory overwrite language-lawyer

说,代码

    class Derived: public Base {....}

    Base* b_ptr = new( malloc(sizeof(Derived)) ) Base(1);
    b_ptr->f(2);
    Derived* d_ptr = new(b_ptr) Derived(3);
    b_ptr->g(4);
    d_ptr->f(5);

似乎合理且LSP满意。

我怀疑当Base和Derived是POD时,此代码是标准允许的,否则不允许(因为vtbl ptr被覆盖)。我的问题的第一部分是:请指出这种覆盖的准确前提条件。

可能存在其他标准允许的写入方式。

我的问题的第二部分是:还有其他方法吗?他们的确切先决条件是什么?

更新:我不想写这样的代码;我对这样的代码的理论可能性(或不可能)很感兴趣。所以,这是“标准纳粹”问题,不是“我怎么能......”的问题。 (我的问题是否已移至其他stackoverflow站点?)

UPDATE2& 4:析构函数怎么样?假设此代码的语义是“基础实例(通过衍生实例片段(破坏性地)更新”)。让我们假设,为简单起见,Base类有一个简单的析构函数。

更新3:对我来说最有趣的是通过b_ptr->g(4)访问

的有效性

4 个答案:

答案 0 :(得分:8)

如果b_ptr = d_ptr子对象不是Derived布局中的第一个,则您需要在Base的新展示位置后Derived进行b_ptr->g(4)。如上所述,basic.life唤起了未定义的行为。

规则(3.8 T):

  

如果在对象的生命周期结束之后,在重用或释放对象占用的存储之前,在原始对象占用的存储位置创建一个新对象,指向该对象的指针原始对象,引用原始对象的引用,或原始对象的名称将自动引用新对象,并且一旦新对象的生命周期开始,就可以   用于操纵新对象,如果

     
      
  • 新对象的存储空间正好覆盖原始对象占用的存储位置,   和
  •   
  • 新对象与原始对象的类型相同(忽略顶级cv-quali firs),
  •   
  • 原始对象的类型不是const-quali fi ed的,如果是类类型,则不包含任何非静态类型   类型为const-quali fi ed或引用类型的数据成员,
  •   
  • 原始对象是类型T的派生程度最高的对象(1.8),新对象是类型basic.life的派生程度最高的对象(也就是说,它们是不是基类子对象)。
  •   

你也应该在重用它的内存之前摧毁旧对象,但标准并没有强制要求。但是,如果不这样做,将泄漏旧对象拥有的任何资源。完整规则在标准的第3.8节({{1}})中给出:

  

程序可以通过重用对象占用的存储或通过使用非平凡的析构函数显式调用类类型的对象的析构函数来结束任何对象的生命周期。对于具有非平凡析构函数的类类型的对象,程序不需要在重用或释放对象占用的存储之前显式调用析构函数;但是,如果没有明确的电话   析构函数或者如果没有使用delete-expression(5.3.5)来释放存储,则不应该隐式调用析构函数,并且依赖于析构函数产生的副作用的任何程序都有未定义的行为

答案 1 :(得分:1)

您正在初始化同一块内存两次。这不会很好。

假设例如Base构造函数分配了一些内存并将其存储在指针中。第二次通过构造函数,第一个指针将被覆盖并且内存泄漏。

答案 2 :(得分:1)

我认为允许覆盖。

如果是我,我可能会在重新使用存储之前调用Base::~Base,这样原始对象的生命周期就会干净利落。但是标准明确允许您在不调用析构函数的情况下重用存储。

我不相信您通过b_ptr访问是有效的。 Base对象的生命周期已结束。

(关于寿命规则的任一标准见3.8 / 4。)

我也不完全相信b_ptr必须提供与malloc()调用最初返回的地址相同的地址。

答案 3 :(得分:1)

如果您更清楚地编写此代码,则更容易看出出现了什么问题:

void * addr = std::malloc(LARGE_NUMBER);

Base * b = new (addr) Base;
b->foo();                    // no problem

Derived * d = new (addr) Derived;
d->bar();                    // also fine  (#1)

b->foo();                    // Error! b no longer points to a Base!

static_cast<Base*>(d)->foo(); // OK
b = d; b->foo();              // also OK

问题在于,在标记为(#1)的行上,bd指向完全分离的,不相关的东西,并且因为你覆盖了以前的对象*b的内存,b实际上已经不再有效了。

您可能对Base*Derived*是可转换指针类型有一些误导的想法,但这与当前情况无关,并且为了这个示例,这两种类型可能是完全没有关系。当我们执行实际转换时,我们使用Derived*可转换为Base*这一事实的最后两行中只有一行。但请注意,此转换是真正的转换,而d static_cast<Base*>(d)的指针不同(至少就语言而言)关注)。

最后,让我们清理一下这个烂摊子:

d->~Derived();
std::free(addr);

摧毁原始*b的机会已经过去,所以我们可能已经泄露了这一点。