首页 > 解决方案 > 具有虚拟和非虚拟析构函数的删除运算符的不同行为

问题描述

当我使用 struct M 的虚拟析构函数时,删除运算符返回后的运算符 new 指向其他地址。

struct M {
    virtual ~M() = default;
};

struct D : public M {
    int* b = nullptr;
};

struct C : public M {
    int* c = nullptr, *b = nullptr;  
    long d = 10;
};

int main() {

    M* f;
    M* d;

    f = new D;
    static_cast<D*>(f)->b = new int(10);
    std::cout << f << ":" << sizeof(*f) << std::endl; // 0x23c1c20 : 8
    delete f;

    d = new C;
    std::cout << d << ":" << sizeof(*d) << std::endl; // 0x23c2c70 : 8
    delete d;

    return 0;
}

但是如果 struct M 的析构函数是非虚运算符 new 返回相同的地址。

struct M {
    ~M() = default;
};

...

int main() {

    M* f;
    M* d;

    f = new D;
    static_cast<D*>(f)->b = new int(10);
    std::cout << f << ":" << sizeof(*f) << std::endl; // 0x23c1c20 : 1
    delete f;

    d = new C;
    std::cout << d << ":" << sizeof(*d) << std::endl; // 0x23c1c20 : 1
    delete d;

    return 0;
}

并且物体的大小是不同的。

为什么会这样?

标签: c++inheritancenew-operatordelete-operator

解决方案


我将从第二个问题开始。

“为什么物体的大小不一样?” - 这virtual是这里的关键。

每个具有虚函数的class/struct都包含一个指向虚表的指针。在这种情况下,M 的大小将等于您机器上指针的大小。我猜你有 64 位机器,指针的大小等于 8 个字节。在删除“virtual”关键字的示例中,空类的大小为 1 个字节。

有关虚函数和表的更多信息,您可以在此处阅读: https ://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/

关于在堆上重用地址内存的第一个问题,我强烈建议您阅读 https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/的第一部分

简而言之,内存的分配是按块来完成的。在第一个示例中(使用虚拟析构函数),两个类都通过指向虚拟表的指针进行扩展。新分配的内存不适合重新分配的内存块,因此找到了新地址。在第二个中,新分配的内存适合释放的空间并被重用。

您可以尝试使用虚函数重新编译您的示例,long d但从struct C. 事实证明,现在地址将是相同的。


推荐阅读