exe调用了cglover.dll中的一个导出类成员函数,是一个读取lua配置文件类,其中一个函数是读取int数组,并通知参数std::vector
结果一出调用函数作用域,就挂调用,挂在std::vector的析构释放内存上~ 郁闷找不到原因,往床上一躺,突然想起关于很早以前就在游戏编程精粹上看到过exe释放dll分配的内存会有问题,但一直没明白为什么,也没有深究,应该是这个问题了。于是上网查了下,然后用windbg跟了下,总算整明白了。
Malloc和Free由CRT提供,分别是
- DEBUG版本:
- MSVCR71D.DLL (C运行时库)
- MSVCP71D.DLL (C++运行时库)
- RELEASE版本:
- MSVCR71.DLL (C运行时库)
- MSVCP71.DLL (C++运行时库)
Malloc和Free内部实际是调用系统提供的HeapAlloc和HeapFree来实现的(Kernel32.dll),这两个API需要HeapCreate创建返回的内存堆HANDLE。
但却不是使用缺省的进程堆(GetProcessHeap), 而是在XXXCRTStartUp入口函数中创建的一个全局句柄HANDLE _crtheap
(入口函数中调用CreateHeap);
其实也并不一定是入口函数中创建,这只是针对静态链接CRT库而已,对于动态链接CRT库的,在整个应用程序中,所有动态链接CRT库的使用同一个_crtheap
(即CRT模块中的_crtheap
),它在ntdll!LdrpCallInitRoutine中调用,并且之后动态链接CRT的模块不再调用heap_init。
经过使用windbg测试,发现Exe或Dll使用静态链接CRT库那么就会在CRTStartup入口函数中静态链接_CRTDLL_INIT中调用的_heap_init
函数(里面调用CreateHeap),但是所有使用动态链接CRT库的模块的就不会在CRTStartup中再调用了,整个应用程序装载的时候之后,确定有模块动态链接CRT库,那么就调用MSVCR71D!_CRTDLL_INIT
,这时就设置了CRT库的全局变量堆句柄_crtheap
,所有动态链接CRT的模块就用到它了。
总结一句,就是这个进程中,所有动态链接CRT库的模块公用CRT模块的heap,而所有静态链接CRT库的模块(包括进程本身)使用自身创建的heap。
动态链接
1 | 。。。 |
静态链接
1 | kernel32!HeapCreate+0x55 (FPO: [3,0,4]) |
就是这样了!
当Dll(分配内存)和Exe(释放内存)都使用静态链接CRT库,那么悲剧发生了:Dll分配使用的是Dll的_crtheap全局句柄分配堆,Exe释放使用Exe的_crtheap全局句柄分配堆。
在Debug模式下会有检查释放内存是否在自身的堆内存block链表中,但在Release模式下却是不可预知的崩溃了。
但是当二者都是使用动态链接CRT DLL,那么malloc和free对应使用的堆句柄就是CRT DLL中的_crtheap了。这依赖于各个模块的编译选项设置(都设置为动态链接CRT库)
所以最根本的错误在于:不同的模块使用了不同的堆。
所以必须保证使用同一个堆。
所以最根本的做法还是,哪个模块分配的,由那个模块来释放。
可选的方法有:
1。在DLL中输出一个函数给EXE调用,专门用来释放由DLL分配的内存;
2。用GlobalAlloc()代替new,用GlobalFree()代替delete;
3。使用单一的堆,分配内存使用HeapAlloc(GetProcessHeap(),0,size),释放内存使用HeapFree(GetProcessHeap(),0,p);
4。把dll和exe的Settings的C/C++选项卡的Code Generation的Use Run-time liberary改成Debug Multithreaded DLL,在Release版本中改成Multithreaded DLL;这样使用一个CRT了——MSVCRT.DLL。
(编者注:在不同版本的vc runtime之间也不能混用new和delete,即使用的都是CRT DLL。)
本文地址:http://xnerv.wang/release-memory-allocated-by-another-module/
转载自:一个模块分配的内存由另外一个模块释放~引发的血案