为什么 Windows 上的向量删除会调用标量删除析构函数?

问题描述 投票:0回答:5

我有一个在 Windows 上泄露的代码。它在许多 UNIX 平台上运行良好,并且泄漏仅发生在 Windows 上。 该二进制文件由 exe、1 个 dll 和 2 个静态库组成。 exe 链接到 dll 和静态库,而静态库也链接到 dll。当出于某种原因调用标量删除析构函数而不是调用向量删除析构函数时,exe 代码中就会发生泄漏。这会导致仅删除数组中的第一个对象,而数组的其余部分保留在内存中。

泄漏的伪代码如下所示:

class MyClassFromExe : public MyBaseClassFromDll {
  public:
    ClassFromDll* m_arr;

    MyClassFromExe(unsigned int size)  
    {
      m_arr = new ClassFromDll[size];
    }

    ~MyClassFromExe() 
    {
      delete [] m_arr;
    }
};

void func()
{
  MyClassFromExe obj(3);
}

当 func() 完成并调用析构函数时,我看到只调用了 m_arr 中第一个对象的析构函数。从调试器中我看到这是通过标量删除析构函数而不是向量删除析构函数完成的。这解释了为什么只有第一个对象被销毁。 我需要理解的是为什么使用delete[]时会调用标量删除析构函数???

我发现了这个线程 - 为什么向量删除析构函数被称为标量删除的结果?。我遵循了那里的建议,并确保所有模块都使用 /MD 进行编译。

值得注意的是,当包含 ClassFromDll 的 dll 是静态库而不是 dll 时,一切工作正常。仅当静态库更改为 dll 时,泄漏才开始。 虽然程序在发布模式下发生泄漏,但在调试模式下删除 [] m_arr 时会崩溃。崩溃发生在 dbgdel.cpp 第 52 行 - _BLOCK_TYPE_IS_VALID(pHead->nBlockUse)。

在unix平台上,这个库也是一个共享库,并且正如预期的那样,向量删除析构函数被调用,并且没有泄漏。难道是VC编译器的问题?或者也许项目的其他一些设置需要更改? 我用的是VC2003。

提前谢谢您!

c++ vector debugging destructor memory-leaks
5个回答
4
投票

这是 VC++ 中有关 DLL 和对象数组的老问题。正如 Microsoft 所解释的,原因是编译器优化不正确。

Microsoft 知识库存档/121216

最好使用STL容器,因为使用分配器,它不会出现问题。


1
投票

DLL 中的类非常敏感,不需要编译器的太多帮助。查看此答案以了解详细信息 为什么这是有问题的:How can I call a function of a C++ DLL that Accepts a parameter of type stringstream from C#?.

简短版本:如果类接口使用任何内联代码,您将遇到与此完全相同的潜在问题。包括任何模板化对象(例如

std::string
)。

我猜这就是原因。这与 @Mikael Persson 提出的问题类似。


0
投票

我认为这是在一个堆上分配并在另一个堆上删除的明显情况(请记住,delete[] 必须在堆中查询数组中的元素数量,如果堆甚至不包含此指针,则它将返回“错误”(不是真的),并且将假定它只是一个元素并使用标量删除代替)。我认为您遇到的问题在尝试将其归结为简单的示例代码时丢失了。我建议您阅读这篇文章(它很旧,但删除技术仍然非常相关,我自己使用该技术的变体,它的作用就像一个魅力)。一种现代的方法是将删除函数指针附加到处理对象的智能指针(shared_ptr),这样,通过在工厂函数中创建对象的同时分配此删除函数指针,您可以确保删除将在分配它的同一堆上调用。


0
投票

一般来说,我建议使用 std::vector 而不是 ClassFromDLL*。构造它并传入 size。然后将自动删除。不幸的是,我对删除[]没有太多经验,因为我总是让标准库为我做这件事;-)


0
投票

查看代码,我假设该对象是由默认复制构造函数复制的,这会导致双重删除错误。这是未定义的行为。它可能看起来可以工作,但由于看似不相关的更改而中断 - 例如从 LIB 切换到 DLL。

© www.soinside.com 2019 - 2024. All rights reserved.