CRT虚拟析构函数

6
今天我遇到一个堆栈损坏的问题,是因为我的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 _declspec(dllexport) CTestClass {...}),并删除每个成员的 declspec,是否会遇到相同的问题?只是好奇。请注意,调用代码和 DLL 应该使用相同的 CRT(调试或发布),因此这是需要考虑的事情。我甚至不确定混合模式是否受支持(我不认为它被支持)。 - WhozCraig
6
你的流程中有多个CRT的副本。你仅导出类方法,而不是v-table。试图推理这些交互以炸毁你的代码并不那么有效,这是可以预期的。导出具有虚拟方法的类需要你导出整个类,在class关键字旁使用__declspec(dllexport)。并且你必须确保单个分配器用于创建和销毁对象。除非你始终使用/MD构建并使用完全相同的编译器版本,否则很难保证。跨模块边界公开C++类只是存在风险。 - Hans Passant
你可能是对的,即使我弄清楚为什么它不起作用,也不会对我有太大帮助。无论如何,还是谢谢你的想法 :) - Poisonbox
3个回答

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 上下文中执行 operator new,然后在该对象上使用 operator delete。如果没有使用虚关键字,则很可能会错过析构函数的调用,从而导致跨上下文删除失败。

你的示例不应该崩溃,因为它没有任何问题。这正是我所想的,然而我将代码分解成了上面所写的部分,结果它确实失败了。 - Poisonbox
你能上传一个示例项目吗?肯定还有其他问题。 - paulm

0
虚拟析构函数只有在存在继承层次结构时才是必要的。虚拟关键字将确保通过在Vtable中查找其析构函数来销毁实际对象的指针(而不是对象类型)。由于在您提供的代码中,CTESTCLASS没有从任何其他类继承,因此它在某种程度上是一个基类,因此不需要虚拟析构函数。我假设可能有另一种底层实现规则导致了这种情况,但您不应该在基类中使用虚拟函数。每当您创建派生对象时,也会创建其基类(出于多态原因),并且基类始终会被销毁(仅当您使其析构函数为虚拟函数时,才会销毁派生对象,从而将其放置在运行时vlookup(虚拟)表中)。
谢谢

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接