当我使用struct M的虚析构函数时,operator 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的析构函数是非虚拟运算符,则返回相同的地址。
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;
}
并且对象的大小不同。
为什么会这样?
我将从第二个问题开始。
“为什么物体的大小不同?” - 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
。可能会发现地址现在是相同的。