【问题标题】:std containers leaking memory on dllstd 容器在 dll 上泄漏内存
【发布时间】:2021-11-21 04:37:16
【问题描述】:

我用 win32 LoadLibrary 加载了一个 dll,当我完成它时,我调用 FreeLibrary,破坏了在 dll 等中分配的所有内存......实际上,内存泄漏问题只发生在 std 容器中。似乎他们不愿意在销毁时释放他们的记忆。这是泄露的代码。

namespace ToolKit
{

  class Game : public GamePlugin
  {
  public:
    void Init(ToolKit::Main* master);
    void Destroy();
    void Frame(float deltaTime, Viewport* viewport);
    void Resize(int width, int height);
    void Event(SDL_Event event);

    std::vector<int> point; // If I remove this line, no leaks are reported.
  };

}

extern "C" TK_GAME_API ToolKit::Game * __stdcall GetInstance()
{
  return new ToolKit::Game(); // Instance is deleted in the caller process than FreeLibrary() is called.
}

GamePlugin 中的所有函数都是无操作的,如果没有 std 容器,进程不会报告任何内存问题。我把漏水堵在这里。为了完成,我将分享我的标准 CRT 内存转储代码。

int main(int argc, char* argv[])
{
  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);    
  return ToolKit_Main(argc, argv);
}

加载和卸载 dll 的代码

  void PluginManager::Load(const String& name)
  {
    HINSTANCE hinstLib;
    TKPROC ProcAdd;
    BOOL fRunTimeLinkSuccess = FALSE;

    String dllName = name;
    hinstLib = LoadLibrary(dllName.c_str());

    if (hinstLib != NULL)
    {
      m_moduleHandle = (void*)hinstLib;
      ProcAdd = (TKPROC)GetProcAddress(hinstLib, "GetInstance");

      if (NULL != ProcAdd)
      {
        fRunTimeLinkSuccess = TRUE;
        m_plugin = (ProcAdd)();
        m_plugin->Init(ToolKit::Main::GetInstance());
      }
    }

    if (!fRunTimeLinkSuccess)
    {
      m_reporterFn("Can not load plugin module " + dllName);
    }
  }

  void PluginManager::Unload()
  {
    if (m_plugin)
    {
      m_plugin->Destroy();
      SafeDel(m_plugin);
    }

    if (m_moduleHandle)
    {
      FreeLibrary((HINSTANCE)m_moduleHandle);
      m_moduleHandle = nullptr;
    }
  }

为了给问题添加更多的澄清,我在这里描述程序流程:

  1. PluginManager::Load 加载 dll
  2. GetInstance 函数是从 dll 中获取的
  3. GetInstance 返回一个插件实例,它存储在 m_plugin 中
  4. PluginManager::Unload 删除 m_plugin 并释放 dll。

这是重现泄漏的最小情况。 进程端:

#include <stdio.h>
#include <cstdlib>
#include <crtdbg.h>
#include <string>
#include <functional>
#include <Windows.h>
#include "Plugin.h"

using namespace std;

class PluginManager
{
public:
  void Load(const string& plugin);
  void Unload();

public:
  GamePlugin* m_plugin = nullptr;
  void* m_moduleHandle = nullptr;
};

typedef GamePlugin* (__cdecl* TKPROC)();
void PluginManager::Load(const string& name)
{
  HINSTANCE hinstLib;
  TKPROC ProcAdd;
  hinstLib = LoadLibrary(name.c_str());

  if (hinstLib != NULL)
  {
    m_moduleHandle = (void*)hinstLib;
    ProcAdd = (TKPROC)GetProcAddress(hinstLib, "GetInstance");

    if (NULL != ProcAdd)
    {
      m_plugin = (ProcAdd)();
      m_plugin->Init();
    }
  }
}

void PluginManager::Unload()
{
  if (m_plugin)
  {
    m_plugin->Destroy();
    delete m_plugin;
  }

  if (m_moduleHandle)
  {
    FreeLibrary((HINSTANCE)m_moduleHandle);
    m_moduleHandle = nullptr;
  }
}

int main(int argc, char* argv[])
{
  _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
  PluginManager* pm = new PluginManager();
  pm->Load("plugin.dll");
  pm->Unload();
  delete pm;
  return 0;
}

插件接口:

#pragma once

#ifdef _WIN32
#    ifdef TK_EXPORTS
#        define TK_GAME_API __declspec(dllexport)
#    else
#        define TK_GAME_API __declspec(dllimport)
#    endif
#elif
#    define TK_GAME_API
#endif

struct GamePlugin
{
  virtual void Init() = 0;
  virtual void Destroy() = 0;
  virtual void Frame() = 0;
};

DLL 端:

#define TK_EXPORTS
#include "Plugin.h"
#include <vector>

class Game : public GamePlugin
{
public:
  void Init() {}
  void Destroy() {}
  void Frame() {}

  std::vector<int> point;
};

extern "C" TK_GAME_API GamePlugin * __stdcall GetInstance()
{
  return new Game();
}

完全一样,如果我们去掉std::vector&lt;int&gt; point,就没有泄漏。

【问题讨论】:

  • 您的假设不正确,FreeLibrary 不会导致调用删除游戏;您必须提供一个显式函数,与 GetInstance 相反,它将删除游戏实例。并注意 GetInstance 调用的数量与 DeleteInstance 调用的数量相匹配,它们应该被引用计数
  • 您真的希望GetInstance() 每次调用时都返回一个新对象吗?如果调用者没有跟踪并删除您有泄漏的实例。如果您尝试实现单例,那么您所拥有的是不正确的。
  • @PKramer 创建实例时,我将它存储在 m_plugin 中并在 PluginManager::unload 中销毁它
  • @RetiredNinja 我调用 getinstance,存储对象,在适当的时候删除对象。名称会导致混淆,getinstance 只调用一次,并且由进程保证。导致泄漏的唯一原因是 std::vector 类,我不知道为什么。
  • 没有泄露。它可重复用于以后的分配。如果您不需要此功能(通常具有更好的性能)并且真的想从检查器中抑制该报告,您可以实现一个始终调用 malloc 和 free 或 malloc_alloc(如果可用)的自定义分配器。

标签: c++ windows dll memory-leaks stdvector


【解决方案1】:

从最小的情况可以看出,我在 dll 中创建插件实例,但是在进程/可执行文件中删除它。即使内存被释放,它也会被报告为泄漏,因为在 dll 中分配的内存并没有在那里释放。哇,真是麻烦啊....

有更好的方法可以做到这一点,但如果有人遇到同样的问题,这里有一个快速解决方法。

流程:

void PluginManager::Unload()
{
  if (m_plugin)
  {
    m_plugin->Destroy();
    m_plugin = nullptr; // Clear the memory in where it is allocated.
  }

  if (m_moduleHandle)
  {
    FreeLibrary((HINSTANCE)m_moduleHandle);
    m_moduleHandle = nullptr;
  }
}

DLL:

void Destroy() { delete this; } // Now CRT Debug is happy. No false leak repots.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-11-09
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多