C++ 多重继承和向上转换的智能指针销毁导致 VS 2017 中的堆损坏
C++ multiple inheritance and upcasted smart pointer destruction causes heap corruption in VS 2017
我在使用上面的代码时遇到了 VS 调试器的问题:
class Animal {
public:
};
class Stupid {
public:
};
class Dog : public Stupid, public Animal {
public:
};
int main() {
std::unique_ptr<Animal> animal = std::unique_ptr<Dog>(new Dog());
animal.reset();
return 0;
}
此代码在执行涉及 "ntdl.dll" 和 "wntdll.pdb" 的 "animal.reset()" 后抛出错误。
如果我点击 "ignore" 多次 (3) 次,以下是 MSVC 运行时库生成断言失败的表达式:
1- _CrtIsValidHeapPointer(block)
2- is_block_type_valid(header->_block_use)
3- HEAP CORRUPTION DETECTED: before Free block (#-50331640) at 0x03737E21. CRT detected that the application wrote to memory before start of heap buffer.
但是如果我改变 Dog 的继承顺序,像这样:
class Dog : public Animal, public Stupid {
public:
};
代码运行良好。
我仅在 visual studio 2017 年遇到此错误,我已尝试使用 Ideone、Android Studio,无论继承顺序如何,它都运行良好。
这会中断,因为您传递给 delete
的指针与您从 new
返回的指针不同。
向上转换基本上意味着您采用指向派生的指针并假装它是指向基的指针。对于单继承,这很有效,因为基础部分总是第一个存储在派生对象中的东西。
但是有了多重继承,你就有了两个基础!
因此,当你向上转换到第二个基地时,你实际上需要改变你的指针的值以确保它指向的实际上是各自的对象的基础部分。您可以通过检查调试器中指针的值来验证:
Dog* d = new Dog;
Animal* a = d;
a
指针将指向 d
指针后一个字节。
如前所述,这可以通过将虚拟析构函数添加到您在删除调用中使用的基 class 类型(在您的示例中为 Animal
)来解决。这将导致编译器在将指针传递给 delete
.
之前生成额外的代码以正确地重新调整指针。
请注意,gcc 实际上在此处实现了空基 class 优化,因此该示例将在那里运行。两个基地将生活在相同的偏移量。一旦您开始向基础 classes.
添加非静态数据成员,它就会在那里开始中断
我在使用上面的代码时遇到了 VS 调试器的问题:
class Animal {
public:
};
class Stupid {
public:
};
class Dog : public Stupid, public Animal {
public:
};
int main() {
std::unique_ptr<Animal> animal = std::unique_ptr<Dog>(new Dog());
animal.reset();
return 0;
}
此代码在执行涉及 "ntdl.dll" 和 "wntdll.pdb" 的 "animal.reset()" 后抛出错误。
如果我点击 "ignore" 多次 (3) 次,以下是 MSVC 运行时库生成断言失败的表达式:
1- _CrtIsValidHeapPointer(block)
2- is_block_type_valid(header->_block_use)
3- HEAP CORRUPTION DETECTED: before Free block (#-50331640) at 0x03737E21. CRT detected that the application wrote to memory before start of heap buffer.
但是如果我改变 Dog 的继承顺序,像这样:
class Dog : public Animal, public Stupid {
public:
};
代码运行良好。
我仅在 visual studio 2017 年遇到此错误,我已尝试使用 Ideone、Android Studio,无论继承顺序如何,它都运行良好。
这会中断,因为您传递给 delete
的指针与您从 new
返回的指针不同。
向上转换基本上意味着您采用指向派生的指针并假装它是指向基的指针。对于单继承,这很有效,因为基础部分总是第一个存储在派生对象中的东西。 但是有了多重继承,你就有了两个基础!
因此,当你向上转换到第二个基地时,你实际上需要改变你的指针的值以确保它指向的实际上是各自的对象的基础部分。您可以通过检查调试器中指针的值来验证:
Dog* d = new Dog;
Animal* a = d;
a
指针将指向 d
指针后一个字节。
如前所述,这可以通过将虚拟析构函数添加到您在删除调用中使用的基 class 类型(在您的示例中为 Animal
)来解决。这将导致编译器在将指针传递给 delete
.
请注意,gcc 实际上在此处实现了空基 class 优化,因此该示例将在那里运行。两个基地将生活在相同的偏移量。一旦您开始向基础 classes.
添加非静态数据成员,它就会在那里开始中断