具有虚拟和非虚拟析构函数的 delete 运算符的不同行为
Different behavior of delete operator with virtual and non-virtual destructor
当我使用struct M的虚拟析构函数时,delete运算符后的operator new return指向其他地址。
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;
}
但是如果结构 M 的析构函数是非虚拟运算符 new return 相同的地址。
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;
}
并且对象的大小不同。
为什么会这样?
我从第二个问题开始
"Why the size of the object is different?" - virtual
是这里的关键。
每个具有虚函数的 class
/struct
都包含一个指向虚函数的指针 table。
在这种情况下,M 的大小将等于您机器上指针的大小。
我猜你有 64 位机器,指针的大小等于 8 个字节。
在删除 'virtual' 关键字的示例中,空 class 的大小为 1 个字节。
有关虚函数和 table 的更多信息,您可以在此处阅读:
https://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/
关于你关于在堆上重用地址内存的第一个问题
我强烈建议你阅读第一部分
https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/
简而言之,内存的分配是通过chunk来完成的。
在第一个示例中(使用虚拟析构函数),两个 classes 都通过指向虚拟 table 的指针进行了扩展。
新分配的内存不适合释放的内存块,因此找到了新地址。
在第二个中,新分配的内存适合释放 space 并被重用。
您可以尝试使用虚函数重新编译您的示例,但从 struct C
中删除了 long d
。现在可能会发现地址是一样的。
当我使用struct M的虚拟析构函数时,delete运算符后的operator new return指向其他地址。
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;
}
但是如果结构 M 的析构函数是非虚拟运算符 new return 相同的地址。
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;
}
并且对象的大小不同。
为什么会这样?
我从第二个问题开始
"Why the size of the object is different?" - virtual
是这里的关键。
每个具有虚函数的 class
/struct
都包含一个指向虚函数的指针 table。
在这种情况下,M 的大小将等于您机器上指针的大小。
我猜你有 64 位机器,指针的大小等于 8 个字节。
在删除 'virtual' 关键字的示例中,空 class 的大小为 1 个字节。
有关虚函数和 table 的更多信息,您可以在此处阅读: https://pabloariasal.github.io/2017/06/10/understanding-virtual-tables/
关于你关于在堆上重用地址内存的第一个问题 我强烈建议你阅读第一部分 https://azeria-labs.com/heap-exploitation-part-1-understanding-the-glibc-heap-implementation/
简而言之,内存的分配是通过chunk来完成的。 在第一个示例中(使用虚拟析构函数),两个 classes 都通过指向虚拟 table 的指针进行了扩展。 新分配的内存不适合释放的内存块,因此找到了新地址。 在第二个中,新分配的内存适合释放 space 并被重用。
您可以尝试使用虚函数重新编译您的示例,但从 struct C
中删除了 long d
。现在可能会发现地址是一样的。