2013-07-08 217 views
6

我遇到了由于我的dll和我的实际项目中的不同CRT设置(MTd MDd)而导致的堆腐败。 我发现奇怪的是,当我将dll中的析构函数设置为虚拟时,应用程序只会崩溃。 有没有一个简单的解释呢?我知道我不能释放那些不在我的堆上的内存,但是当我将析构函数定义为非虚拟的时候,其中的差异究竟在哪里。CRT虚拟析构函数

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

的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 
} 
+0

此外,你是否有同样的问题,通过将declspec移动到* class *('class _declspec(dllexport)CTestClass {...}')并删除每个成员declspecs?只是好奇。注意,调用代码和DLL应该使用相同的CRT(调试或发布),因此需要考虑。我甚至不确定混合模式是否受支持(我不认为它是)。 – WhozCraig

+6

您的流程中有多个CRT副本。你只导出类方法,而不是v表。试图理解这一切如何相互作用来轰炸你的代码并不是那么有效,这是可以预料的。使用虚拟方法导出类需要导出整个类,并将__declspec(dllexport)放在* class *关键字旁边。而且您必须确保使用单个分配器来创建和销毁该对象。很难保证,除非你始终用/ MD编译并使用完全相同的编译器版本。在模块边界上暴露C++类是有风险的。 –

+0

你是对的,即使我找出为什么它不起作用,它不会帮助我太多。感谢你的想法:) – Poisonbox

回答

2

class CTestClass 
{ 
public: 
    _declspec(dllexport) CTestClass() {} 
    _declspec(dllexport) virtual ~CTestClass() {} 
}; 

__declspec(dllexport) class CTestClass 
{ 
public: 
    CTestClass() {} 
    virtual ~CTestClass() {} 
}; 

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

0

你真的不交足够的代码以确保万无一失。但是,你的例子应该不会崩溃,因为没有什么错:

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的上下文中执行上删除。如果没有虚拟关键字,你可能会错过执行交叉上下文删除的析构函数调用。

+0

“你的例子不应该崩溃,因为它没有任何问题”,这正是我的想法,但是我把代码分解成了上面所写的内容,事实上它确实如此失败。 – Poisonbox

+0

你可以上传一个示例项目吗?一定有其他事情出错了 – paulm

0

虚拟析构函数只有当你有一些继承层次树时才需要。 Virtual关键字将确保指向实际对象(而不是对象的类型)的指针通过在Vtable中查找其析构函数而被销毁。由于在这个例子中,通过你给出的代码,CTestClass不是从任何其他类继承的,它以一种基类的方式,因此不需要虚拟析构函数。我假设有可能是另一个引擎盖实现规则,但不应该在基类中使用虚拟。任何时候你创建一个派生的对象,你也创建它的基础(为了多态的原因)并且基础总是被销毁(如果你为它创建了析构函数,派生只会被销毁,因此将它放入运行时的虚拟表) 。

感谢