CRT 虚拟析构函数

Posted

技术标签:

【中文标题】CRT 虚拟析构函数【英文标题】:CRT virtual destructor 【发布时间】:2013-07-06 01:49:14 【问题描述】:

我今天在我的 dll 和我的实际项目中遇到了不同的 CRT 设置 (MTd MDd) 导致的堆损坏。 我发现奇怪的是,只有当我将 dll 中的析构函数设置为虚拟时,应用程序才会崩溃。 有一个简单的解释吗?我知道我无法释放不在我的堆上的内存,但是当我将析构函数定义为非虚拟时,区别到底在哪里。

一些代码只是为了让它更清晰一点

DLL

#pragma once
class CTestClass

public:
    _declspec(dllexport) CTestClass() ;
    _declspec(dllexport) virtual ~CTestClass() ;
;

还有我的项目

int main(int argc, char* argv[])

    CTestClass *foo = new CTestClass;
    delete foo; // Crashes if the destructor is virtual but works if it's not

【问题讨论】:

另外,将 declspec 移动到 class (class _declspec(dllexport) CTestClass ...) 并删除每个成员的 declspecs 是否有同样的问题?只是好奇。请注意,调用代码和 DLL 应该使用相同的 CRT(调试或发布),因此需要考虑。我什至不确定是否支持混合模式(我认为不支持)。 您的进程中有多个 CRT 副本。你只导出类方法,而不是 v-table。试图推理出这一切如何相互作用来轰炸你的代码并不是那么有成效,这是意料之中的。使用虚方法导出类需要导出整个类,将 __declspec(dllexport) 放在 class 关键字旁边。并且您必须确保使用单个分配器来创建和销毁对象。除非您始终使用 /MD 构建并使用完全相同的编译器版本,否则很难保证。跨模块边界公开 C++ 类是有风险的。 你说得对,即使我弄清楚它为什么不起作用,它也不会对我有太大帮助。无论如何感谢您的想法:) 【参考方案1】:

有区别

class CTestClass

public:
    _declspec(dllexport) CTestClass() 
    _declspec(dllexport) virtual ~CTestClass() 
;

__declspec(dllexport) class CTestClass

public:
     CTestClass() 
     virtual ~CTestClass() 
;

在前一种情况下,您指示编译器仅导出两个成员函数:CTestClass::CTestClass() 和 CTestClass::~CTestClass()。但在后一种情况下,您将指示编译器也导出虚函数表。一旦你有一个虚拟析构函数,这个表是必需的。所以这可能是坠机的原因。当您的程序尝试调用虚析构函数时,它会在关联的虚函数表中查找它,但它没有正确初始化,因此我们不知道它真正指向的位置。如果您的析构函数不是虚拟的,那么您不需要任何虚拟函数表,一切正常。

【讨论】:

【参考方案2】:

您并没有真正发布足够的代码来确定。但是您的示例不应该崩溃,因为它没有任何问题:

int main(int argc, char* argv[])

    // 1. Allocated an instance of this class in *this/exe* heap, not the DLL's heap
    // if the constructor allocates memory it will be allocated from the DLL's heap
    CTestClass *foo = new CTestClass;

    // 2. Call the destructor, if it calls delete on anything it will be freed from the DLL's heap since thats where the destructor is executing from. Finally we free the foo object from *this/exe* heap - no problems at all.
    delete foo;

我怀疑在您的真实代码中,您必须对一个在 dll 上下文中执行 operator new 的对象使用 operator delete。如果没有 virtual 关键字,您可能会错过执行跨上下文删除的析构函数调用。

【讨论】:

“你的例子不应该崩溃,因为它没有任何问题”,这正是我的想法,但是我将代码分解为上面写的内容,事实上它确实如此失败。 你能上传一个示例项目吗?肯定是有什么问题【参考方案3】:

只有当你有一些继承层次树时才需要虚拟析构函数。 Virtual 关键字将确保指向实际对象(不是对象的类型)的指针通过在 Vtable 中找到其析构函数来销毁。由于在此示例中,按照您提供的代码,CTestClass 没有从任何其他类继承,它在某种程度上是一个基类,因此不需要虚拟析构函数。我假设可能有另一个在幕后实现规则导致了这种情况,但你不应该将 virtual 与基类一起使用。任何时候你创建一个派生对象,你也创建了它的基础(出于多态的原因)并且基础总是被破坏(如果你将它的析构函数设为虚拟,派生的只会被破坏,因此将它放置在运行时 vlookup(虚拟)表中) .

谢谢

【讨论】:

以上是关于CRT 虚拟析构函数的主要内容,如果未能解决你的问题,请参考以下文章

C++——虚析构

没有虚拟构造函数,只有虚拟析构函数

具有虚拟析构函数的基类子类中的默认析构函数

C++ 虚拟析构函数 (virtual destructor)

使用非虚拟析构函数有啥具体原因吗?

直接调用(虚拟)析构函数是不是有效?