根本原因是new和delete跨模块调用导致堆不一致:windows下dll默认独立crt堆,linux下so若链接不同libc版本也会出现此问题;a模块new的内存被b模块delete会破坏堆结构,引发崩溃。

跨 DLL/SO 传递裸指针时 delete 崩溃的根本原因
崩溃不是因为“指针传错了”,而是因为 new 和 delete 发生在不同模块的堆上。Windows 下每个 DLL 默认使用独立的 C 运行时(CRT)堆,Linux 下共享库若链接了不同版本的 libc 或启用了 -fPIC + 静态链接 CRT,也可能导致堆不一致。用 A 模块的 new 分配的内存,被 B 模块的 delete 释放,会破坏堆管理结构,触发断言失败或直接访问违规。
std::shared_ptr 不能解决跨模块释放问题
很多人尝试把裸指针包进 std::shared_ptr 并传给另一模块,但如果不显式指定自定义删除器,shared_ptr 的析构仍会在**接收方模块的堆上执行 delete** —— 问题照旧。关键点在于:删除逻辑必须和分配逻辑落在同一堆上下文。
- 错误写法:
std::shared_ptr<int>(raw_ptr)</int>→ 析构时调用当前模块的operator delete - 正确写法:必须绑定分配方提供的释放函数,例如
std::shared_ptr<int>(raw_ptr, [](int* p) { deallocate_in_module_A(p); })</int> - 更稳妥的做法是:分配方同时导出
create_XXX()和destroy_XXX(void*)两个 C 风格函数,接收方只调用后者
推荐方案:统一堆 + RAII 封装(非裸指针)
最可靠的方式是让所有动态内存都在**同一个 CRT 堆**上分配和释放。具体操作取决于平台:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- Windows:DLL 和 EXE 都链接
/MD(动态 CRT),避免/MT;确保所有模块用相同版本的 VCRT(如 v143) - Linux:所有 SO 使用系统
libc的malloc/free,不静态链接glibc;C++ 分配器保持默认,不重载operator new - 跨语言/跨 ABI 场景(如 Python 调 C++ DLL):彻底放弃裸指针,改用句柄(
int或void*仅作 ID)、序列化数据、或内存池预分配
如果必须暴露对象接口,用 PIMPL + 工厂函数 + 显式销毁函数,例如:
extern "C" {
MyObjHandle create_obj();
void destroy_obj(MyObjHandle h);
int get_value(MyObjHandle h);
}
调试时怎么快速定位是不是跨堆释放?
崩溃堆栈里如果出现 HeapFree、RtlFreeHeap(Windows)或 __libc_free(Linux)内部断言失败,且调用链横跨多个模块,基本可锁定。更直接的方法:
- Windows:用 Application Verifier 开启 “Heap” 选项,或用
!heap -p -a <addr></addr>在 WinDbg 中查该内存块归属哪个堆 - Linux:运行时设置
MALLOC_TRACE=./malloc.log,再用mallocstats分析;或用 AddressSanitizer 编译,它对跨堆释放有明确报错heap-use-after-free或invalid-pointer-delete - 加日志:在分配和释放函数开头打印
_get_heap_handle()(Windows)或malloc_usable_size(ptr)(Linux),比对是否一致
真正麻烦的不是“不知道怎么修”,而是有人在头文件里声明 MyClass* create(); void destroy(MyClass*); 却没注明这两个函数必须由同一模块调用 —— 这种隐式契约最容易被忽略,也最难被编译器捕获。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










