Win32环境下DLL中STL容器内存泄漏问题求助
这个问题的核心根源是跨模块CRT堆不兼容导致的内存分配/释放不匹配,STL容器只是把这个潜在问题放大了而已。让我一步步拆解:
为什么带STL容器会泄漏?
当你的DLL里用new创建Game对象时,对象里的std::vector会在DLL的CRT堆上分配内部存储内存。而你在主程序里调用delete m_plugin时,Game的析构函数会触发vector的析构,尝试释放内存——但如果主程序和DLL使用的是不同的CRT实例(比如一个静态链接CRT,一个动态链接;或者两者都是静态链接但版本不同),释放操作会用主程序的CRT堆来操作DLL堆分配的内存,这就会导致内存泄漏(甚至崩溃,只是调试器只报了泄漏)。
如果去掉std::vector,Game对象本身的内存虽然也是跨模块分配/释放,但因为没有额外的堆内存分配,调试器可能没检测到这个潜在问题——但实际上这也是不符合规范的跨模块内存操作,只是没暴露出来而已。
解决方案
1. 统一使用动态链接CRT(最简单首选)
把主程序和DLL的项目设置都改成使用动态链接CRT:
- Debug模式:选择
/MDd(Multi-threaded Debug DLL) - Release模式:选择
/MD(Multi-threaded DLL)
这样主程序和DLL会共享同一个CRT堆实例,内存分配和释放可以跨模块正常工作,STL容器的内存也能正确释放。
2. 在DLL内部负责对象销毁(最安全的规范做法)
不要让主程序直接deleteDLL创建的对象,而是在DLL里导出一个专门的销毁函数,让内存的分配和释放都在同一个模块(DLL)的CRT环境中完成:
修改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(); } // 新增:导出销毁函数 extern "C" TK_GAME_API void __stdcall DestroyInstance(GamePlugin* plugin) { delete static_cast<Game*>(plugin); }
修改主程序的PluginManager::Unload:
// 新增函数指针类型 typedef void (__stdcall* DESTROYPROC)(GamePlugin*); void PluginManager::Unload() { if (m_plugin && m_moduleHandle) { m_plugin->Destroy(); // 获取DLL里的销毁函数 DESTROYPROC destroyProc = (DESTROYPROC)GetProcAddress((HINSTANCE)m_moduleHandle, "DestroyInstance"); if (destroyProc) { destroyProc(m_plugin); m_plugin = nullptr; } } if (m_moduleHandle) { FreeLibrary((HINSTANCE)m_moduleHandle); m_moduleHandle = nullptr; } }
这种方法完全避免了跨模块内存操作的问题,即使未来切换CRT模式也不会出问题,是插件架构的标准做法。
3. 自定义STL分配器(复杂不推荐)
给STL容器指定一个跨模块兼容的分配器,比如使用系统堆(HeapAlloc/HeapFree)来分配内存,确保分配和释放都用同一个堆。但这个方法需要自定义分配器类,代码复杂度高,除非有特殊需求,否则不推荐。
总结
优先选择方案1(统一动态CRT)快速解决问题,长期来看方案2(DLL内部销毁)是更健壮的架构设计,能避免很多潜在的跨模块问题。
内容的提问来源于stack exchange,提问作者Cihan

